STUDY AREA

ggplot() +
  geom_sf(data=srme) +
  geom_sf(data=wrnf, fill="darkgrey") +
  geom_sf(data=blocks, fill=NA, lwd=0.4) +
  coord_sf(crs="EPSG:32613") +
  theme_light(11)

Figure 1: Spectral Response and Phenological Characteristics

Load the Sentinel-2 annual time-series (2019).


# Spectral Response
ts <- read_csv('../../data/tabular/mod/results/s2msi_l2a_aspen_TSy2019.csv', 
               show_col_types = FALSE) %>% 
  select(-c(id,.geo,TCB,TCG,TCW)) %>%
  rename(NDVI705 = NDRE)

head(ts)
NA

Tidy the time-series data.

  1. Remove cloud-contaminated pixels,
  2. Pivot the table to gather bands,
  3. Generate mean/median/stdev of reflectance by image date,
  4. Calculate weekly summaries

# Filter out cloud-contaminated pixels based on the Cloud Score + values
# Reference:  https://medium.com/google-earth/all-clear-with-cloud-score-bd6ee2e2235e

tsp <- ts %>%
  filter(
    cs >= 0.8, # >= Cloud Score + contamination
  ) %>%
  # add the month as a column
  mutate(month = month(image_date, label=TRUE, abbr=TRUE)) %>%
  # Filter null values
  filter(complete.cases(.))

# Test using some visualizations

# NDVI705 scatter plot with trend line
tsp %>%
  group_by(image_date) %>%
  summarize(band = median(NDVI705)) %>%
  ggplot(aes(x=image_date,y=band)) +
  geom_point(size=0.8) +
  geom_smooth(method="loess") +
  labs(x="Image Date", y="NDRE") +
  theme_bw(11)


# NDVI705 box plot
ggplot(data=tsp, aes(x=NDVI705, y=month)) +
  geom_boxplot() +
  coord_flip() +
  labs(y="Image Date", x="NDRE") +
  theme_bw(11)


rm(ts)

Now pivot the table longer:


tsp_ <- tsp %>%
  select(c(starts_with("B"),image_date,
           SLAVI,NDVI705,NDMI,ChlRE,IRECI,MCARI)) %>%
  group_by(image_date) %>%
  summarize_all(list(median)) %>%
  pivot_longer(
    cols = -c(image_date),
    names_to="band",
    values_to="reflectance"
  ) %>%
  # add a month and week label
  mutate(
    month = month(image_date, label=TRUE, abbr=TRUE),
    week = isoweek(image_date),
    biweek = cut.Date(image_date, breaks="2 week", labels=FALSE)
  )

head(tsp_, n=19)

rm(tsp)
gc()
           used  (Mb) gc trigger   (Mb) limit (Mb)  max used   (Mb)
Ncells  4738541 253.1    8904811  475.6         NA   8904811  475.6
Vcells 71359190 544.5  243030996 1854.2      16384 243028662 1854.2

Breakpoint analysis to identify significant shift in spectral signature throughout the year based on NDRE:


# Isolate one of the vegetation indices (Normalized Difference Red-edge Index)
df <- tsp_ %>% 
  filter(band == "NDVI705") %>% 
  select(image_date,week,reflectance) %>%
  group_by(image_date,week) %>%
  summarize(reflectance = median(reflectance)) %>%
  ungroup()
`summarise()` has grouped output by 'image_date'. You can override using the `.groups` argument.
# Create a weekly time-series object
df.ts <- ts(df$reflectance, frequency = 52)

# Implement the changepoint analysis based on NDRE
result <- changepoint::cpt.meanvar(df.ts, method="PELT")
bp.ind <- changepoint::cpts(result)

# Map breakpoints to dates
print("Breakpoint weeks:")
[1] "Breakpoint weeks:"
bp.dates <- df$week[bp.ind]
bp.dates
[1] 11 17 21 26 43 47
# Plot the result
plot(result)


rm(df, df.ts, result, bp.ind, bp.dates)

Perform the analysis by spatial block grid to get an idea of the variation in time-series breakpoints.

results <- list()

for(block in unique(df$block_id)) {
    # Subset data for the block
    block_data <- df %>% filter(block_id == block)

    # Create time series (assuming weekly aggregation)
    block_ts <- ts(block_data$reflectance, frequency=52)

    # Perform breakpoint analysis
    block_result <- changepoint::cpt.meanvar(block_ts, method="PELT")

    # Store results with block identifier
    results[[block]] <- changepoint::cpts(block_result)
}
Warning: Unknown or uninitialised column: `block_id`.
# Create an empty data frame to store breakpoint information
breakpoint_data <- data.frame(block_id = character(), breakpoint_week = integer())

# Loop through each block and add its breakpoints to the data frame
for(block in names(results)) {
    block_breakpoints <- results[[block]]
    block_data <- data.frame(block_id = rep(block, length(block_breakpoints)), 
                             breakpoint_week = block_breakpoints)
    breakpoint_data <- rbind(breakpoint_data, block_data)
}

# Convert the 'breakpoint_week' to actual dates
# This conversion depends on your specific date arrangement. Here's a basic example:
start_date <- as.Date("2019-01-02")  # Replace with your dataset's start date
breakpoint_data$breakpoint_date <- start_date + (breakpoint_data$breakpoint_week - 1) * 7

ggplot(breakpoint_data, aes(x = breakpoint_date, y = block_id, group = block_id)) +
    geom_line() +  # Optionally connect breakpoints in each block
    geom_point(size = 3) +  # Breakpoint markers
    theme_minimal() +
    labs(x = "Date", y = "Spatial Block") +
    theme(panel.grid.major.y = element_blank(),  # Removes horizontal grid lines
          panel.grid.minor.y = element_blank())

Plot of median daily observations:


# Reorder band factors
tsp_$band <- factor(tsp_$band, 
                   levels = c("B2","B3","B4","B5","B6","B7","B8","B8A","B11","B12",
                              "ChlRE","IRECI","NDMI","NDVI705","SLAVI","MCARI"))

tsp_ %>%
  filter(str_detect(band,"B")) %>%
  ggplot(aes(x=image_date,y=reflectance ,color=factor(band))) +
  geom_line(linewidth=0.4) +
  scale_color_viridis_d(option="turbo") +
  labs(x="Acquisition Date",y="Reflectance",color="S2 MSI Band") +
  theme_bw(14) +
  theme(axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        legend.text = element_text(size=10),
        legend.title = element_text(size=11))

Figure 3: Spectral Response of Aspen Forest Presence Data

Gather the seasonal composite start and end dates:

# Summer
(start_summer <- week(as.Date("2019-06-06")))
[1] 23
(end_summer <- week(as.Date("2019-08-25")))
[1] 34
# Autumn
(start_autumn <- week(as.Date("2019-09-27")))
[1] 39
(end_autumn <- week(as.Date("2019-11-01")))
[1] 44

Generate the weekly median reflectance.


# List for band colors

band_colors <- c(
  "B2"  = "#1E90FF",  # Blue: Dodger Blue
  "B3"  = "#00FF7F",  # Green: Spring Green
  "B4"  = "#FF0000",  # Red: Pure Red
  "B5"  = "#FFA07A",  # Red Edge: Light Salmon
  "B6"  = "#FF8C00",  # NIR: Dark Orange
  "B7"  = "#D2691E",  # NIR: Chocolate
  "B8"  = "#FFD700",  # NIR: Gold
  "B8A" = "#B8860B",  # NIR: Dark Goldenrod
  "B11" = "#008B8B",  # SWIR: Dark Cyan (dark shade of teal)
  "B12" = "#800080"   # SWIR: Purple
)


# Group by week, calculate the mean, plot

f3a <- tsp_ %>%
  filter(str_detect(band,"B")) %>%
  group_by(band,week) %>%
  summarize(refl = mean(reflectance)) %>%
  ggplot(aes(x=week,y=refl,color=factor(band))) +
  geom_line(linewidth=0.6) +
  scale_color_manual(values = band_colors) +
  labs(x="Acquisition Week",y="Reflectance",color=NULL) +
  theme_bw(11) +
  theme(axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        legend.text = element_text(size=8),
        legend.title = element_text(size=8),
        legend.position = c(0.2,0.95),
        legend.direction = "horizontal",
        legend.background = element_rect(fill="white", color="black"),
        plot.margin = unit(c(1.2,0.5,1.2,0.5), 'lines'),
        text = element_text(family = "Arial")) +
  annotate('rect', xmin=start_summer, xmax=end_summer, ymin=0, ymax=6000, alpha=.3, fill='gray') +
  annotate('rect', xmin=start_autumn, xmax=end_autumn, ymin=0, ymax=6000, alpha=.3, fill='gray') +
  geom_vline(xintercept=start_summer, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=end_summer, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=start_autumn, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=end_autumn, linetype="dotted", lwd=0.8)

f3a

Figure 1B: Time-series charts of the spectral indices:


# All bands

# Calculate the maximum reflectance for each band
max_refl_band <- tsp_ %>%
  group_by(band) %>%
  summarize(max_refl = max(reflectance))

# Now create your plot
tsp_ %>%
  group_by(band, week) %>%
  summarize(refl = mean(reflectance), .groups = 'drop') %>%
  ggplot(aes(x = week, y = refl, group = 1)) +
  geom_line() +
  geom_rect(data = max_refl_band, aes(xmin=start_summer, xmax=end_summer, ymin=0, ymax=max_refl), alpha=.3, fill='gray',
            inherit.aes = FALSE) +
  geom_rect(data = max_refl_band, aes(xmin=start_autumn, xmax=end_autumn, ymin=0, ymax=max_refl), alpha=.3, fill='gray',
            inherit.aes = FALSE) +
  geom_vline(xintercept = start_summer, linetype = "dotted", lwd = 0.8) +
  geom_vline(xintercept = end_summer, linetype = "dotted", lwd = 0.8) +
  geom_vline(xintercept = start_autumn, linetype = "dotted", lwd = 0.8) +
  geom_vline(xintercept = end_autumn, linetype = "dotted", lwd = 0.8) +
  facet_wrap(. ~ band, scales = "free_y") +
  theme_minimal() +
  theme(axis.title.y = element_text(size = 8, face = "italic",
                                    margin = unit(c(0, 0.4, 0, 0), "cm")),
        axis.title.x = element_text(size = 8, face = "italic",
                                    margin = unit(c(0.4, 0, 0, 0), "cm")),
        axis.text = element_text(size = 7),
        strip.text.x = element_text(size = 8))


# Spectral indices

max_refl_band_vi <- max_refl_band %>%
  filter(!str_detect(band,"B"))

f3b <- tsp_ %>%
  filter(!str_detect(band,"B")) %>%
  group_by(band,week) %>%
  summarize(refl = mean(reflectance)) %>%
  ggplot(aes(x=week, y=refl, group=1)) +
  geom_line(linewidth=0.6) +
  labs(x="Acquisition Week",y="") +
  geom_rect(data = max_refl_band_vi, aes(xmin = start_summer, xmax = end_summer, ymin = 0, ymax = max_refl), alpha = .3, fill = 'gray',
            inherit.aes = FALSE) +
  geom_rect(data = max_refl_band_vi, aes(xmin = start_autumn, xmax = end_autumn, ymin = 0, ymax = max_refl), alpha = .3, fill = 'gray',
            inherit.aes = FALSE) +
  geom_vline(xintercept=start_summer, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=end_summer, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=start_autumn, linetype="dotted", lwd=0.8) +
  geom_vline(xintercept=end_autumn, linetype="dotted", lwd=0.8) +
  facet_wrap(. ~ band, scales = "free") +
  theme_light(11) +
  theme(axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11),
        plot.margin = unit(c(1.2,0.5,1.2,0.5), 'lines'),
        text = element_text(family = "Arial"))
f3b


rm(max_refl_band,max_refl_band_vi)

Figure 3C: Boxplot of seasonal composites:


cols <- c(
  "Summer" = "#87CEEB", 
  "Autumn" = "#DAA520"
)

seasonal <- tsp_ %>%
  mutate(season = if_else(week>=22&week<=37,"Summer","na"),
         season = if_else(week>=37&week<=48,"Autumn",season)) %>%
  filter(season!="na")

seasonal$season <- factor(seasonal$season, levels = c("Summer","Autumn"))
  
f3c <- seasonal %>%
  filter(str_detect(band,"B")) %>%
  ggplot(aes(x=band,y=reflectance,fill=season)) +
  geom_boxplot(outlier.size = 0.5, outlier.color = "grey30") +
  scale_fill_manual(values = cols) +
  theme_bw(12) +
  labs(x="Sentinel-2 MSI Band",y="Reflectance",fill="Season: ") +
  theme(axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        legend.position = "none",
        plot.margin = unit(c(1.2,0.5,1.2,0.5), 'lines'),
        text = element_text(family = "Arial"))
f3c

Figure 1D: Seasonal differences in the spectral indices


f3d <- seasonal %>%
  filter(!str_detect(band,"B")) %>%
  ggplot(aes(y=reflectance)) +
  geom_boxplot(aes(fill=season)) +
  scale_fill_manual(values = cols) +
  facet_wrap(. ~ band, scales = "free") +
  theme_bw(12) +
  labs(fill="Season",y="") +
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank(),
        axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11),
        # legend.position = c(0.85,0.2),
        legend.position = "bottom",
        legend.title = element_text(size=12),
        legend.text = element_text(size=12),
        legend.spacing.y = unit(1.5, "cm"),
        legend.spacing.x = unit(0.5, 'cm'),
        plot.margin = unit(c(1.2,0.5,1.2,0.5), 'lines'),
        text = element_text(family = "Arial"))
f3d

Combine the plots:


# Arrange in a multi-panel plot

f3 <- ggarrange(f3a,f3b,f3c,f3d, nrow=2,ncol=2, widths=c(1, 0.75), labels = c("A", "B", "C", "D"))
f3


# Save it out

ggsave(f3, file = "../../figures/Figure3_SpectralResponse.png",
       dpi = 300, bg="white") # adjust dpi accordingly

Version 2:


f3_ <- ggarrange(f3a,ggarrange(f3c,f3d,nrow=1,ncol=2),
                  ncol=1,widths=c(1, 0.75),
                  labels = c("A", "B", "C"))
Warning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font database
f3_


ggsave(f3_, file = "../../figures/Figure3_SpectralResponse_v2.png",
       dpi = 300, bg="white") # adjust dpi accordingly
Saving 14 x 8 in image

Clean up the time-series / spectral response data:

rm(f3,f3a,f3b,f3c,f3d,seasonal,band_colors,cols,end_autumn,end_summer,start_autumn,start_summer,tsp_)
Warning: object 'f3' not foundWarning: object 'f3a' not foundWarning: object 'f3b' not foundWarning: object 'f3c' not foundWarning: object 'f3d' not foundWarning: object 'seasonal' not foundWarning: object 'band_colors' not foundWarning: object 'cols' not foundWarning: object 'end_autumn' not foundWarning: object 'end_summer' not foundWarning: object 'start_autumn' not foundWarning: object 'start_summer' not found

Figure 4: Model accuracy and feature importance plot

Best Performing Model:

Load the accuracy metrics for the TEST data:

glimpse(accmeas)
Rows: 1,000
Columns: 16
$ model     <dbl> 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845…
$ prSize    <dbl> 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059, 2059,…
$ bgSize    <dbl> 18722, 18722, 18722, 18722, 18722, 18722, 18722, 18722, 18722, 18722, 18722, 18722, 18722, 18722, 18722, 18722, 1…
$ cutoff    <dbl> 0.00000000, 0.01010101, 0.02020202, 0.03030303, 0.04040404, 0.05050505, 0.06060606, 0.07070707, 0.08080808, 0.090…
$ tp        <dbl> 2059, 2040, 2014, 1997, 1981, 1969, 1962, 1949, 1940, 1933, 1922, 1921, 1914, 1902, 1894, 1888, 1885, 1882, 1878,…
$ tpr       <dbl> 1.0000000, 0.9907722, 0.9781447, 0.9698883, 0.9621175, 0.9562895, 0.9528898, 0.9465760, 0.9422050, 0.9388052, 0.9…
$ fn        <dbl> 0, 19, 45, 62, 78, 90, 97, 110, 119, 126, 137, 138, 145, 157, 165, 171, 174, 177, 181, 184, 187, 196, 200, 207, 2…
$ tn        <dbl> 0, 11629, 15711, 16465, 16874, 17140, 17356, 17527, 17676, 17811, 17913, 17989, 18042, 18101, 18160, 18214, 18243…
$ tnr       <dbl> 0.0000000, 0.6211409, 0.8391732, 0.8794466, 0.9012926, 0.9155005, 0.9270377, 0.9361713, 0.9441299, 0.9513407, 0.9…
$ fp        <dbl> 18722, 7093, 3011, 2257, 1848, 1582, 1366, 1195, 1046, 911, 809, 733, 680, 621, 562, 508, 479, 449, 422, 392, 359…
$ fpr       <dbl> 1.000000000, 0.378859096, 0.160826835, 0.120553360, 0.098707403, 0.084499519, 0.072962290, 0.063828651, 0.0558700…
$ accuracy  <dbl> 0.09908089, 0.65776430, 0.85294259, 0.88840768, 0.90731919, 0.91954189, 0.92959915, 0.93720225, 0.94393918, 0.950…
$ precision <dbl> 0.09908089, 0.22336582, 0.40079602, 0.46944053, 0.51736746, 0.55449169, 0.58954327, 0.61991094, 0.64969859, 0.679…
$ recall    <dbl> 1.0000000, 0.9907722, 0.9781447, 0.9698883, 0.9621175, 0.9562895, 0.9528898, 0.9465760, 0.9422050, 0.9388052, 0.9…
$ f1        <dbl> 0.1802977, 0.3645461, 0.5686053, 0.6326628, 0.6728940, 0.7019608, 0.7284203, 0.7491832, 0.7690783, 0.7884968, 0.8…
$ mcc       <dbl> NA, 0.3683508, 0.5703012, 0.6289086, 0.6653778, 0.6919874, 0.7168437, 0.7360157, 0.7549327, 0.7737948, 0.7872590,…

Calculate the model averages using the optimum cutoff value:

# Without taking the average

print("~~~~~Total (not averageing) based on maximum F1~~~~~")
[1] "~~~~~Total (not averageing) based on maximum F1~~~~~"
paste0("Cutoff: ",(cutoffOptF1 = accmeas[which.max(accmeas$f1),]$cutoff))
[1] "Cutoff: 0.393939393939394"
paste0("Precision: ",(precisionOptF1 = accmeas[which.max(accmeas$f1),]$precision))
[1] "Precision: 0.918444165621079"
paste0("Recall: ",(recallOptF1 = accmeas[which.max(accmeas$f1),]$recall))
[1] "Recall: 0.899631298648095"
paste0("FPR: ",(fprOpt = accmeas[which.max(accmeas$f1),]$fpr))
[1] "FPR: 0.00880520184231915"
paste0("TPR: ",(tprOpt = accmeas[which.max(accmeas$f1),]$tpr))
[1] "TPR: 0.899631298648095"
paste0("Max F-score: ",(max_f1 = max(accmeas$f1, na.rm=TRUE)))
[1] "Max F-score: 0.908940397350993"
# With the aggregation/averaging across models and threshold values

# Calculate the averages for each cutoff value across model runs

accmeas.mn <- accmeas %>%
  mutate(cutoff = as.character(cutoff)) %>%
  group_by(cutoff) %>%
  summarise(
    prMn = mean(prSize,na.rm=T), # size of presence data (mean)
    prSd = sd(prSize,na.rm=T), # size of presence data (sd)
    bgMn = mean(bgSize,na.rm=T), # size of background data (mean)
    bgSd = sd(bgSize,na.rm=T), # size of background data (sd)
    fprMn = mean(fpr,na.rm=T), # False Positive Rate (mean)
    fprSd = sd(fpr,na.rm=T),
    tprMn = mean(tpr,na.rm=T), # True Positive Rate (mean)
    tprSd = sd(tpr,na.rm=T),
    precision = mean(precision,na.rm=T), # precision (mean)
    precisionSd = sd(precision,na.rm=T), 
    recall = mean(recall,na.rm=T), # recall (mean)
    recallSd = sd(recall,na.rm=T),
    f1 = mean(f1,na.rm=T), # F1 score (mean)
    f1Sd = sd(f1,na.rm=T),
    mcc = mean(mcc,na.rm=T), # Matthew's Correlation Coefficient (mean)
    mccSd = sd(mcc,na.rm=T),
    accuracy = mean(accuracy,na.rm=T), # Overall accuracy (mean)
    accuracySd = sd(accuracy,na.rm=T)
  ) %>%
  ungroup() %>%
  mutate(cutoff = round(as.double(cutoff),3))

# Calculate optimum threshold based on F1 statistic

print("~~~~~Model averages and maximum F-score~~~~~")
[1] "~~~~~Model averages and maximum F-score~~~~~"
(cutoffOptMn = accmeas.mn[which.max(accmeas.mn$f1),]$cutoff)
[1] 0.404
(precisionOptF1Mn = accmeas.mn[which.max(accmeas.mn$f1),]$precision)
[1] 0.9197149
(recallOptF1Mn = accmeas.mn[which.max(accmeas.mn$f1),]$recall)
[1] 0.8674155
(fprOptMn = accmeas.mn[which.max(accmeas.mn$f1),]$fprMn)
[1] 0.008385747
(tprOptMn = accmeas.mn[which.max(accmeas.mn$f1),]$tprMn)
[1] 0.8674155
(max_f1Mn <- max(accmeas.mn$f1, na.rm = TRUE))
[1] 0.8923609
# F1 based
print("F1-based accuracy metrics")
[1] "F1-based accuracy metrics"
paste0("F1 Score: ",(f1Opt = accmeas.mn[which.max(accmeas.mn$f1),]$f1))
[1] "F1 Score: 0.892360920780445"
paste0("Overall Accuracy: ",(oaOpt = accmeas.mn[which.max(accmeas.mn$f1),]$accuracy))
[1] "Overall Accuracy: 0.979342797214287"
paste0("Precision: ",(precOpt = accmeas.mn[which.max(accmeas.mn$f1),]$precision))
[1] "Precision: 0.919714918739564"
paste0("Recall: ",(recOpt = accmeas.mn[which.max(accmeas.mn$f1),]$recall))
[1] "Recall: 0.867415486483451"
paste0("Cutoff: ",(threshOpt = accmeas.mn[which.max(accmeas.mn$f1),]$cutoff))
[1] "Cutoff: 0.404"
# Mathews Correlation Coefficient
print("---------------------------")
[1] "---------------------------"
print("MCC-based accuracy metrics")
[1] "MCC-based accuracy metrics"
paste0("MCC: ",(mccOpt = accmeas.mn[which.max(accmeas.mn$mcc),]$mcc))
[1] "MCC: 0.881682092429702"
paste0("Overall Accuracy: ",(oaOptmcc = accmeas.mn[which.max(accmeas.mn$mcc),]$accuracy))
[1] "Overall Accuracy: 0.97939150487788"
paste0("Precision: ",(precOptmcc = accmeas.mn[which.max(accmeas.mn$mcc),]$precision))
[1] "Precision: 0.923102951396921"
paste0("Recall: ",(recOptmcc = accmeas.mn[which.max(accmeas.mn$mcc),]$recall))
[1] "Recall: 0.864141436357284"
paste0("Cutoff: ",(threshOptmcc = accmeas.mn[which.max(accmeas.mn$mcc),]$cutoff))
[1] "Cutoff: 0.414"

Plot the model accuracy results for the optimum threshold.


# Get the best F1 score for each model run
accmeas.best <- accmeas %>% 
  group_by(factor(model)) %>% 
  top_n(1,f1) %>%
  ungroup() %>%
  mutate(model_iter = row_number())

print(paste0("Cutoff: Mean = ",round(mean(accmeas.best$cutoff),3),"; Standard Deviation: ",round(sd(accmeas.best$cutoff),3)))
[1] "Cutoff: Mean = 0.388; Standard Deviation: 0.056"
print(paste0("F1 Score: Mean = ",round(mean(accmeas.best$f1),3),"; Standard Deviation: ",round(sd(accmeas.best$f1),3)))
[1] "F1 Score: Mean = 0.895; Standard Deviation: 0.013"

Check on the optimum threshold by spatial block:

Figure 4A: Model Accuracy (F1 and OA)


cols <- c("F1 Score"="#1f78b4","Overall Accuracy"="gray75")

f4a <- ggplot(data=accmeas.best) +
  geom_hline(yintercept=0.972760973096824,linetype="dashed",color="gray85") +
  geom_hline(yintercept=0.910339477355629,linetype="dashed",color="#1f78b4") +
  geom_line(aes(x=factor(model_iter),y=accuracy,group=1),color="gray65") +
  geom_line(aes(x=factor(model_iter),y=f1,group=1),color="#1f78b4") +
  geom_point(aes(x=factor(model_iter),y=f1,size=f1), color="#1f78b4") +
  geom_point(aes(x=factor(model_iter),y=accuracy,size=accuracy), color="gray65") +
  scale_size(range = c(3,6)) +
  coord_cartesian(ylim = c(0.85, 1)) +
  labs(x="Model Iteration", y="Score", tag="A") +
  annotate("text", x = 3, y = 0.99, label = paste("Average Overall Accuracy: ", round(oaOpt, 2))) +
  annotate("text", x = 2.6, y = 0.88, label = paste("Average F1 Score: ", round(f1Opt, 2))) +
  theme_bw() +
  guides(size="none") +
  theme(
        plot.margin = unit(c(0.2,0.2,0.2,0.2), 'lines'),
        axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11),
        text = element_text(family = "Arial"))
f4a

ggsave(f4a, file = "../../figures/Figure4A_Best_Model_Acc_F1.png",
       dpi = 300, bg="white") # adjust dpi accordingly

Figure 4B: Sensitivity Analysis


# Labels for each model subset

labels_ = c(
  "winter_sar" = "M1", 
  "summer_sar" = "M2",
  "summer_winter_sar_text" = "M3",
  "summer_winter_sar" = "M4",
  "autumn_spectral" = "M5",
  "summer_spectral" = "M6",
  "summer_spectral_ind" = "M7",
  "spectral_sar_ind" = "M8",
  "summer_autumn_spectral" = "M9",
  "spectral_sar" = "M10",
  "final" = "Final Model"
)

# Add in the final model to the sensitivity analysis

add <- accmeas.best %>%
  ungroup() %>%
  select(model,f1) %>%
  mutate(subset = "final") %>%
  rename(f1Max = f1)
glimpse(add)
Rows: 10
Columns: 3
$ model  <dbl> 847, 991, 343, 197, 783, 9, 283, 777, 588, 708
$ f1Max  <dbl> 0.8906140, 0.9021872, 0.9219364, 0.9084126, 0.9225152, 0.9348000, 0.8911798, 0.9283032, 0.9264555, 0…
$ subset <chr> "final", "final", "final", "final", "final", "final", "final", "final", "final", "final"
accmeas.s <- accmeas.s %>%
  bind_rows(add)
rm(add)

print(unique(accmeas.s$subset))
 [1] "autumn_spectral"        "spectral_sar_ind"       "spectral_sar"           "summer_autumn_spectral"
 [5] "summer_sar"             "summer_spectral_ind"    "summer_spectral"        "summer_winter_sar_text"
 [9] "summer_winter_sar"      "winter_sar"             "final"                 
# Generate the box plots of F1 score

f4b <- ggplot(data=accmeas.s, aes(x=reorder(subset,f1Max),y=f1Max)) +
  geom_boxplot() +
  scale_x_discrete(labels=labels_) +
  labs(x="Input Features",y="Maximum F1 Score", tag="B") +
  theme_bw(11) +
  theme(
        plot.margin = unit(c(0.2,0.2,0.2,0.2), 'lines'),
        axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11),
        axis.text.x = element_text(angle = 45, hjust=1),
        text = element_text(family = "Arial"))
f4b

# Save out

ggsave(f4b, file = "../../figures/Figure4B_Model_Sensitivity.png",
       dpi = 300, bg="white") # adjust dpi accordingly
Saving 7.29 x 4.51 in image

Combine the two figures for Figure 4AB:


f4 <- ggarrange(f4a,f4b,nrow=2)
Warning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font database
f4


ggsave(f4, file = "../../figures/Figure4AB_Model_Accuracy_Sensitivity.png",
       width=7.5, height=7.5, dpi = 300, bg="white") # adjust dpi accordingly

Figure 6: Feature importance from the best model

glimpse(ftr_imp)
Rows: 10
Columns: 58
$ `system:index`     <dbl> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
$ B11_autumn         <dbl> 23.96951, 15.42196, 17.38912, 20.77920, 17.98646, 14.35781, 20.22814, 18.89352, 21.39317, 21.74230
$ B11_summer         <dbl> 57.76175, 41.57353, 55.38727, 47.29878, 57.65944, 66.39970, 52.62610, 52.36947, 64.38439, 55.44989
$ B12_autumn         <dbl> 11.98532, 12.18155, 12.43531, 12.99580, 10.57533, 11.77865, 10.22154, 10.58631, 13.44953, 12.67882
$ B12_summer         <dbl> 41.56687, 34.67877, 39.44705, 35.01835, 40.73719, 45.57362, 43.94695, 35.44048, 40.34988, 45.13620
$ B2_autumn          <dbl> 24.39883, 18.75472, 20.84065, 20.19995, 20.07801, 21.99199, 25.17718, 27.36519, 23.79532, 27.95109
$ B2_summer          <dbl> 6.701419, 5.685158, 7.487585, 8.295692, 5.797926, 7.355428, 6.634612, 5.799903, 6.133081, 7.241619
$ B3_autumn          <dbl> 77.03881, 65.23082, 69.94465, 71.90851, 67.38341, 76.92625, 58.84659, 65.00396, 68.12063, 75.00733
$ B3_summer          <dbl> 19.46012, 20.67046, 18.43354, 21.31853, 19.24631, 24.06442, 17.23488, 21.10171, 17.11757, 22.19655
$ B4_autumn          <dbl> 27.98196, 22.38539, 22.77445, 28.96994, 22.49855, 23.71713, 20.13873, 22.20413, 23.15649, 23.53728
$ B4_summer          <dbl> 14.18891, 13.73221, 15.11904, 15.67287, 15.57566, 15.11593, 13.98516, 14.70856, 14.73648, 14.61585
$ B5_autumn          <dbl> 27.88235, 25.17227, 22.34128, 27.90425, 23.01650, 27.43090, 23.60410, 27.61166, 28.53207, 25.57357
$ B5_summer          <dbl> 20.51127, 21.43604, 16.91838, 19.58951, 19.10655, 22.20925, 19.90399, 19.53389, 22.42832, 26.14503
$ B6_autumn          <dbl> 8.542329, 5.959136, 6.393729, 8.162880, 4.491474, 7.473760, 5.087818, 6.228571, 7.790806, 8.588132
$ B6_summer          <dbl> 5.709577, 5.000908, 6.083143, 7.212246, 7.608573, 6.938815, 7.852073, 6.787830, 6.602138, 7.222599
$ B7_autumn          <dbl> 4.922737, 2.367098, 3.498418, 4.203459, 2.652523, 3.258887, 2.969710, 3.458624, 4.354459, 3.675525
$ B7_summer          <dbl> 12.68438, 10.49479, 10.46613, 10.16754, 11.02767, 11.86213, 10.33427, 11.54215, 11.23929, 12.07572
$ B8A_autumn         <dbl> 6.847360, 4.164674, 5.025483, 6.479871, 4.057279, 3.322784, 4.836362, 4.366135, 5.085010, 3.997957
$ B8A_summer         <dbl> 10.186781, 11.117060, 10.975642, 11.375302, 10.671619, 10.651220, 9.582447, 8.323220, 7.979682, 10.827156
$ B8_autumn          <dbl> 5.648152, 3.788238, 2.936567, 4.901975, 3.982437, 3.379598, 3.770422, 3.117721, 3.883635, 3.729896
$ B8_summer          <dbl> 11.69722, 12.58247, 12.06786, 12.57490, 13.94528, 12.50597, 12.37619, 11.74377, 14.16442, 13.33923
$ CIRE_autumn        <dbl> 12.682218, 9.948294, 12.185743, 10.532118, 11.118460, 11.421446, 8.279047, 12.388113, 10.407604, 13.6272…
$ CIRE_summer        <dbl> 64.95096, 60.32539, 63.35120, 54.39878, 55.25884, 57.57070, 59.26430, 57.21251, 59.50161, 66.59634
$ IRECI_autumn       <dbl> 2.637017, 2.899772, 2.407293, 2.292840, 2.324381, 2.896016, 1.890673, 2.202049, 2.115453, 2.392068
$ IRECI_summer       <dbl> 38.42998, 33.60194, 39.22338, 32.35176, 33.04609, 35.26569, 29.73309, 30.95488, 37.30331, 38.80681
$ MCARI_autumn       <dbl> 0.47320435, 0.18627721, 0.06680252, 0.10972856, 0.22017827, 0.42895421, 0.25032529, 0.28998688, 0.108599…
$ MCARI_summer       <dbl> 5.202777, 2.869336, 6.814850, 4.620712, 4.505827, 3.868291, 2.984218, 4.829247, 2.574470, 5.344253
$ MNDWI_autumn       <dbl> 158.7695, 118.0827, 153.8095, 155.2914, 150.8991, 162.1784, 151.2204, 155.1730, 169.7797, 168.2181
$ MNDWI_summer       <dbl> 15.666369, 8.811929, 13.187401, 11.070450, 14.833296, 14.959725, 15.307943, 15.106241, 13.289381, 13.117…
$ NDVI705_autumn     <dbl> 15.92025, 15.33948, 12.37923, 16.93214, 16.30455, 15.62460, 12.93668, 15.32293, 15.62903, 13.88636
$ NDVI705_summer     <dbl> 66.31226, 62.72652, 75.23845, 58.22471, 59.22142, 62.38631, 61.09397, 60.33121, 70.95350, 71.42434
$ SLAVI_autumn       <dbl> 5.950147, 5.810316, 4.532684, 6.284397, 6.823756, 5.200986, 3.204204, 4.718834, 6.120556, 5.512967
$ SLAVI_summer       <dbl> 39.98426, 39.89840, 46.23012, 35.70791, 38.48014, 37.97482, 46.44509, 43.41672, 45.87035, 41.81807
$ VH_summer          <dbl> 120.3291, 125.4076, 114.1734, 117.0543, 119.0975, 111.5540, 112.5779, 106.5729, 106.6080, 111.1643
$ VH_summer_contrast <dbl> 0.6912977, 0.6443750, 1.1025728, 0.8196579, 0.5428332, 1.0492712, 0.2673518, 0.4408656, 0.8477107, 0.761…
$ VH_summer_corr     <dbl> 2.038716e-02, 4.689808e-02, -5.421011e-20, 1.981763e-02, 2.602085e-18, 0.000000e+00, 2.295281e-02, 3.630…
$ VH_summer_ent      <dbl> 0.8903305, 0.4434135, 1.0235408, 0.5779195, 0.6776606, 0.9702269, 0.4580580, 0.3225830, 0.8961926, 0.722…
$ VH_summer_var      <dbl> 0.1178149, 0.1364224, 0.3232861, 0.2389374, 0.2948422, 0.1828724, 0.1820762, 0.2104632, 0.4274647, 0.256…
$ VH_winter          <dbl> 51.73159, 39.36654, 41.06588, 39.85520, 39.64370, 39.63047, 44.50477, 42.21597, 38.70772, 40.62742
$ VH_winter_contrast <dbl> 1.659763, 2.709168, 1.147402, 1.543892, 1.207093, 2.452500, 1.485999, 1.190308, 1.952913, 1.009504
$ VH_winter_corr     <dbl> 2.186988e-02, 9.648835e-03, 1.682235e-02, 0.000000e+00, 5.421011e-20, 5.421011e-20, 5.421011e-20, 0.0000…
$ VH_winter_ent      <dbl> 0.9772491, 2.0892041, 1.0394168, 1.5766948, 1.0146580, 1.5568744, 1.6067600, 0.9185858, 1.4541630, 1.253…
$ VH_winter_var      <dbl> 0.4543666, 0.8225297, 0.1882809, 0.4200083, 0.1877988, 0.3270226, 0.2778561, 0.4152783, 0.5643009, 0.692…
$ VV_summer          <dbl> 67.06995, 60.04802, 58.81295, 65.01515, 59.76840, 50.86551, 54.75310, 57.36608, 52.15202, 59.79460
$ VV_summer_contrast <dbl> 0.15968231, 0.22301413, 0.19891426, 0.11199894, 0.11580189, 0.25417250, 0.09430409, 0.19623888, 0.242593…
$ VV_summer_corr     <dbl> 2.089830e-02, -2.710505e-20, 1.379565e-02, 2.710505e-20, 6.036042e-02, 1.720349e-02, 4.951494e-02, 1.577…
$ VV_summer_ent      <dbl> 0.1263584, 0.1651060, 0.3023428, 0.2979435, 0.4760067, 0.1751426, 0.1992779, 0.2251015, 0.2532914, 0.488…
$ VV_summer_var      <dbl> 8.878294e-02, 9.516638e-02, 8.748314e-02, 1.779121e-01, 2.333934e-01, 2.579175e-02, 7.728387e-02, 6.7762…
$ VV_winter          <dbl> 27.69455, 25.89087, 25.83059, 23.01626, 21.10866, 22.61843, 24.39041, 23.47822, 22.63203, 20.03053
$ VV_winter_contrast <dbl> 0.24576644, 0.37570895, 0.38384506, 0.11255678, 0.17450437, 0.44031287, 0.11329419, 0.03551684, 0.236508…
$ VV_winter_corr     <dbl> 2.293942e-02, 0.000000e+00, 1.897354e-19, 0.000000e+00, 1.660088e-02, 0.000000e+00, 2.715249e-02, 0.0000…
$ VV_winter_ent      <dbl> 0.06966621, 0.29831500, 0.25767645, 0.09146932, 0.11229459, 0.21209068, 0.12259783, 0.17863952, 0.161149…
$ VV_winter_var      <dbl> 0.119294700, 0.019575725, 0.036722857, 0.004419991, 0.088261557, 0.030941733, 0.015672022, 0.095319866, …
$ aspect             <dbl> 1.541063e-01, 1.436701e-01, 2.242213e-01, 1.626303e-19, 5.935381e-02, 8.543204e-02, 7.635836e-02, 1.1760…
$ elevation          <dbl> 82.68177, 52.92215, 68.29378, 101.56002, 65.84898, 87.31860, 77.21677, 72.38847, 76.90134, 87.06744
$ seed               <dbl> 845, 344, 318, 653, 44, 475, 23, 712, 229, 128
$ slope              <dbl> 8.504462, 8.877512, 6.629997, 9.208848, 6.155771, 8.813523, 8.253295, 9.377661, 6.757827, 8.313743
$ .geo               <chr> "{\"type\":\"MultiPoint\",\"coordinates\":[]}", "{\"type\":\"MultiPoint\",\"coordinates\":[]}", "{\"type…

# # Tidy the data frame
# df.imp <- ftr_imp %>% 
#   select(-c(.geo,`system:index`)) %>%
#   rename(model = seed) %>%
#   mutate(model = as.factor(model)) %>%
#   # Summarize across to grab the relative importance
#   rowwise() %>%
#   mutate(total = sum(c_across(B11_autumn:VV_winter_ent))) %>%
#   ungroup() %>%
#   mutate(across(B11_autumn:VV_winter_ent, ~ . * 100 / total)) %>%
#   select(-total)
# glimpse(df.imp)

# Tidy the data frame
df.imp <- ftr_imp %>% 
  select(-c(.geo,`system:index`)) %>%
  rename(model = seed) %>%
  mutate(model = as.factor(model)) %>%
  # Summarize across to grab the relative importance
  group_by(model) %>%
  mutate(total = sum(across(B11_autumn:slope))) %>%
  mutate(across(B11_autumn:slope, ~ . / total)) %>%
  ungroup() %>%
  select(-total)

# Pivot longer
df.imp.p <- df.imp %>%
  pivot_longer(cols = -model) %>%
  rename(importance = value,
         band = name) %>%
  mutate(season = if_else(str_detect(band,"_autumn"), "Autumn Spectral", "Summer Spectral"),
         season = if_else(str_detect(band,"_winter"), "Winter SAR", season),
         season = if_else(str_detect(band,"VV_summer"), "Summer SAR", season),
         season = if_else(str_detect(band,"VH_summer"), "Summer SAR", season))
glimpse(df.imp.p)
Rows: 550
Columns: 4
$ model      <fct> 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 845, 84…
$ band       <chr> "B11_autumn", "B11_summer", "B12_autumn", "B12_summer", "B2_autumn", "B2_summer", "B3_autumn", "B3_summer", "B4_…
$ importance <dbl> 1.980106e-02, 4.771661e-02, 9.900996e-03, 3.433813e-02, 2.015571e-02, 5.536000e-03, 6.364126e-02, 1.607588e-02, …
$ season     <chr> "Autumn Spectral", "Summer Spectral", "Autumn Spectral", "Summer Spectral", "Autumn Spectral", "Summer Spectral"…

# Grab the top 20 over all model runs

top <- df.imp.p %>%
  group_by(band) %>%
  summarize(median = median(importance)) %>%
  ungroup()

top <- head(arrange(top,desc(median)), n = 20)

# Color palette

cols <- c(
  "Summer Spectral" = "#87CEEB", 
  "Autumn Spectral" = "#DAA520",
  "Winter SAR" = "gray29",
  "Summer SAR" = "gray89"
)

# Boxplot

# %>% filter(band %in% top$band)
f6 <- ggplot(data=df.imp.p %>% filter(band %in% top$band), 
              aes(x=reorder(band,importance), fill=season)) +
  geom_boxplot(aes(y=importance), position = position_dodge(0.5)) +
  scale_fill_manual(values = cols, 
                    labels = c("Autumn Spectral","Summer SAR","Summer Spectral","Winter SAR")) +
  coord_flip() +
  theme_bw(11) +
  theme(axis.text.x = element_text(size=11),
        axis.text.y = element_text(angle = 0, vjust=0, size=11),
        axis.title = element_text(size=11,face="italic"),
        legend.position = "bottom",
        legend.justification = c(1.2,0.5), # Left-aligns the legends
        legend.text = element_text(size=11),
        text = element_text(family = "Arial"),
        plot.margin=unit(c(0.5,0.5,0.5,0.5),"cm")) +
  labs(x="Feature", y="Importance", fill="Season: ")
f6

ggsave(f6, file = "../../figures/Figure6_FeatureImportance.png",
       dpi=300, bg="white") # adjust dpi accordingly

Clean up!

rm(list = ls.str(mode = 'numeric'))
rm(accmeas,accmeas.best,accmeas.mn,accmeas.s,df.imp,
   df.imp.p,f4,f4a,f4b,f6,ftr_imp,top,labels_,cols)
gc()
           used  (Mb) gc trigger   (Mb) limit (Mb)  max used   (Mb)
Ncells  4066144 217.2   13000420  694.3         NA  16250524  867.9
Vcells 43892752 334.9  148975796 1136.6      16384 186212038 1420.7

Figures 7, 8 - Case Study: Landscape Patch Dynamics

Figure 7: Spatial Agreement


cols <- c("#4DAF4A", "#984EA3", "#FF7F00")
flabs <- c(prec = "Precision", rec = "Recall", f1 = "F1-score")

# Reshape the agreement table for plotting facet wrap

# Southern Rockies (by block)
ref.srme_m <- reshape2::melt(
  ref.srme, id.vars = c("blocksize", "source", "region", "grid_id"), 
  measure.vars = c("prec", "rec", "f1"), variable.name = "statistic"
) %>%
  na.omit()

# WRNF as a whole
ref.wrnf_m <- reshape2::melt(
  ref.wrnf, id.vars = c("blocksize", "source", "region"), 
  measure.vars = c("prec", "rec", "f1"), variable.name = "statistic"
) %>%
  na.omit()

# Facet wrap plot of the statistics by block group

f7 <- ggplot(data=ref.srme_m, aes(x=factor(blocksize), y=value, fill=factor(source))) +
  geom_boxplot(position = position_dodge()) + 
  scale_fill_manual(values=cols, labels=c("LANDFIRE EVT","USFS TreeMap","USFS ITSP"), name="Source: ") +
  facet_wrap(~statistic, labeller = labeller(statistic = flabs)) +
  theme_light(11) +
  theme(legend.position = "top",
        legend.box = "vertical",
        legend.justification = c(0.5,0.5), # Left-aligns the legends
        legend.spacing.y = unit(-10, "pt"), # Adjusts the gap between the legends
        legend.text = ggtext::element_markdown(halign = 0), # This aligns each individual legend's text to the left
        axis.title = element_text(size=12,face="italic"),
        text = element_text(family = "Arial")) +
  labs(x="Blocksize",y="Value")

# Add the WRNF statistics
f7 <- f7 +
  # Plot the WRNF points on top as a different shape
  geom_point(data=ref.wrnf_m, aes(x=factor(blocksize), y=value, shape=region), 
             position = position_dodge(width = 0.75), size = 3, color="black") +
  scale_shape_manual(name="Subregion", values=c("WRNF" = 8)) +
  guides(
    fill=guide_legend(override.aes=list(shape=NA)), # No shape in the fill legend
    shape=guide_legend(title="Subregion", override.aes=list(color="black")) # Shape legend for Subregion
  )

f7


# Add a spatial map of statistics by blocks
blocks_ <- blocks %>%
  left_join(ref.srme, by="grid_id") %>%
  pivot_longer(cols = c(prec, rec, f1), names_to = "statistic", values_to = "value")

ggplot(data = blocks_) +
  geom_sf(aes(fill = value)) +
  facet_grid(source ~ statistic) +
  scale_fill_viridis_c(option = "C") + # Use a continuous color scale
  theme_void() +
  theme(
    strip.text.x = element_text(size = 10),
    legend.position = "bottom"
  ) +
  guides(fill = guide_colourbar(direction = "horizontal", barwidth = 10, barheight = 0.60,
                                ticks=F, title.position = "left"),
         label.theme = element_text(angle = 0, size = 9)) +
  labs(fill="")


brewer.pal(n=5,"Set1")
[1] "#E41A1C" "#377EB8" "#4DAF4A" "#984EA3" "#FF7F00"
cols <- c("#4DAF4A", "#984EA3", "#FF7F00")

glimpse(ref.global)
Error in glimpse(ref.global) : object 'ref.global' not found

Focal accuracy:


# ref.focal$refbudens <- log(1 + ref.focal$tp + ref.focal$fn)
# 
# ggplot(ref.focal %>% filter(region=="SRME"), aes(x = prec, y = rec, color = refbudens)) +
#    geom_point(size = 2, alpha = 0.9) +
#    scale_color_viridis_c() +
#    facet_grid(rows = vars(geog_scale), cols = vars(blocksize), scales = "fixed") +
#    theme_light() +
#    labs(
#       title = 'Spatially explicit accuracy: Precision (x) vs. Recall (y)',
#       subtitle = 'for multiple analytical units and spatial support levels',
#       color = 'refbudens',
#       x = 'Precision',
#       y = 'Recall'
#    ) +
#    coord_cartesian(xlim = c(0, 1), ylim = c(0, 1)) +
#    theme(legend.position = "bottom")

Join the two plots:

# f7 <- ggarrange(global_precrec,focal.prec,focal.rec,nrow=1,labels=c("A","B","C"))
# f7
# ggsave(f7, file="../../figures/Figure7_Global_Focal_Agreement.png", dpi=300)

Clean up!

rm(ref.global,ref.global.m,f7a,f7b,f7,ref.focal)
Warning: object 'f7b' not foundWarning: object 'f7' not found
gc()
           used  (Mb) gc trigger   (Mb) limit (Mb)  max used   (Mb)
Ncells  3500531 187.0   10554481  563.7         NA  10554481  563.7
Vcells 42536015 324.6  239958304 1830.8      16384 299930662 2288.3

Figure 8: Landscape Patch Dynamics

unique(factor(patch_metrics$source))
[1] LFEVT    ITSP     TreeMap  Aspen10m Aspen30m
Levels: Aspen10m Aspen30m ITSP LFEVT TreeMap
summary(patch_metrics)
       X               index              area             perimeter        perimeter_area_ratio  shape_index     
 Min.   :      0   Min.   :      0   Min.   :     0.01   Min.   :      40   Min.   :  72.95      Min.   :  1.000  
 1st Qu.: 180181   1st Qu.: 180181   1st Qu.:     0.05   1st Qu.:     100   1st Qu.:1000.00      1st Qu.:  1.000  
 Median : 489868   Median : 489868   Median :     0.09   Median :     120   Median :1333.33      Median :  1.000  
 Mean   : 594419   Mean   : 594419   Mean   :     1.72   Mean   :     561   Mean   :1728.38      Mean   :  1.249  
 3rd Qu.: 920401   3rd Qu.: 920401   3rd Qu.:     0.27   3rd Qu.:     280   3rd Qu.:2333.33      3rd Qu.:  1.333  
 Max.   :1822292   Max.   :1822292   Max.   :145661.31   Max.   :16467240   Max.   :4000.00      Max.   :119.887  
    region             source         
 Length:5145088     Length:5145088    
 Class :character   Class :character  
 Mode  :character   Mode  :character  
                                      
                                      
                                      

Calculate some grouped statistics (to compare with the class metrics results)


# Also create a grouped summary
(patch.summary <- patch_metrics %>%
  group_by(source,region) %>%
  summarize(area_md = median(area), # convert to hectares
            area_mn = mean(area),
            area_sum = sum(area),
            perim_md = median(perimeter),
            perim_mn = mean(perimeter),
            par_md = median(perimeter_area_ratio),
            par_mn = mean(perimeter_area_ratio),
            si_md = median(shape_index),
            si_mn = mean(shape_index)) %>%
  ungroup())

(patch.long <- patch.summary %>%
  pivot_longer(cols = c(contains("_")),
               names_to = "metric",
               values_to = "value"))

patch.long$source <- factor(patch.long$source,levels=c('Aspen10m', 'Aspen30m', 'LFEVT', 'TreeMap', 'ITSP'))

(ggplot(data = patch.long, aes(x=source, y=value, fill=region)) +
    geom_bar(stat="identity", position="dodge", width=0.7) +
    facet_wrap(~metric, scales="free") + 
    theme_minimal(14) +
    theme(axis.text.x = element_text(angle = 45, hjust = 1),
    text = element_text(family = "Arial")))


(f8s <- ggplot(data = patch.long, aes(x=source, y=value, fill=region)) +
    geom_bar(stat="identity", position="dodge", width=0.7) + 
    theme_minimal(14) +
    theme(axis.text.x = element_text(angle = 45, hjust = 1),
    text = element_text(family = "Arial")))

Grab some summary statistics on the patch dynamics:

a <- patch.long %>% filter(region=="SRME",metric=="area_mn",source=="Aspen10m")
b <- patch.long %>% filter(region=="SRME",metric=="area_mn",source=="LFEVT")

print("Difference in mean patch size for the SRME: ")
[1] "Difference in mean patch size for the SRME: "
((b$value-a$value)/b$value)*100
[1] 70.66201
a <- patch.long %>% filter(region=="WRNF",metric=="area_mn",source=="Aspen10m")
b <- patch.long %>% filter(region=="WRNF",metric=="area_mn",source=="LFEVT")

print("Difference in mean patch size for the WRNF: ")
[1] "Difference in mean patch size for the WRNF: "
((b$value-a$value)/b$value)*100
[1] 78.58107

Boxplot as facet wrap for patch metrics:


brewer.pal(n=5,"Set1")
[1] "#E41A1C" "#377EB8" "#4DAF4A" "#984EA3" "#FF7F00"
cols <- c("#005a32","#a1d99b")

(df <- patch_metrics %>%
  # mutate(area_ha = area*100) %>%
  select(-c(X,index,shape_index)) %>%
  pivot_longer(contains(c("area","perimeter_area_ratio"))) %>%
  arrange(source, desc(value)) %>%
  mutate(name = as.character(name),
         name = recode(name,
                       "area" = "Patch Size (ha)",
                       "perimeter_area_ratio" = "Perimeter/Area Ratio")))

df$source <- factor(df$source,
                    levels=c('Aspen10m', 'Aspen30m', 'LFEVT', 'TreeMap', 'ITSP'))
 
(f8a <- ggplot(data=df, aes(y = value, x = factor(source), fill = factor(region))) +
  geom_boxplot(outlier.size = 0.2) +
  scale_y_continuous(trans="log10",labels=label_number_si(accuracy = 1)) +
  scale_fill_manual(values=cols) +
  facet_wrap(~name, scales = "free_y", nrow=1) +
  labs(x="\nData Source",y="Value",title="Patch Metrics", fill="Region: ") +
  theme_light(12) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, size=11),
        axis.text.y = element_text(angle = 0, vjust=0.1, size=11),
        axis.title.y = element_text(size=12, face="italic", vjust=1),
        axis.title.x = element_text(size=12, face="italic", vjust=-1),
        plot.margin = unit(c(0.5,0.5,0.5,0.5),"cm"),
        legend.position="top",
        legend.spacing.x = unit(0.5,"cm"),
        text = element_text(family = "Arial")))
Warning: `label_number_si()` was deprecated in scales 1.2.0.
Please use the `scale_cut` argument of `label_number()` instead.

ggsave(f8a, file = "../../figures/Figure8A_patch_metrics.png", dpi = 300) # adjust dpi accordingly
Saving 10 x 5 in image

Plot class metrics as facet wrap:


# Calculate area in square meters
srme_area_km2 <- st_area(srme) / 1e6
wrnf_area_km2 <- st_area(wrnf) / 1e6

(df <- class_metrics %>%
  mutate(total_area = total_area*0.01,
         prop_area = if_else(region=="SRME", as.double((total_area/srme_area_km2*100)), 0.0),
         prop_area = if_else(region=="WRNF", as.double((total_area/wrnf_area_km2*100)), prop_area)) %>%
  select(-c(X)) %>%
  pivot_longer(cols = c(contains("_")),
               names_to = "metric",
               values_to = "value") %>%
  mutate(metric = as.character(metric),
         metric = recode(metric,
                         "n_patch" = "Number of Patches",
                         "patch_den" = "Patch Density",
                         "total_area" = "Total area (km2)",
                         "prop_area" = "Proportion of Area")))

df$source <- factor(df$source, levels=c('Aspen10m', 'Aspen30m', 'LFEVT', 'TreeMap', 'ITSP'))
head(df)

(f8b <- ggplot(data=df, aes(y=value, x=factor(source), fill=factor(region))) +
  geom_bar(stat="identity", position="dodge", width=0.7) +
  scale_y_continuous(labels=scales::label_number_si(accuracy = 1)) +
  scale_fill_manual(values=cols) +
  labs(x="", y="Value", title="Landscape Metrics", fill="Region: ") +
  theme(axis.title.x = NULL) +
  facet_wrap(~metric, scales = "free_y") +
  theme_light(12) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, size=11),
        axis.text.y = element_text(angle = 0, vjust=0.1, size=11),
        axis.title.y = element_text(size=12, face="italic", vjust=0.5),
        axis.title.x = element_text(size=12, face="italic", vjust=-0.5),
        plot.margin = unit(c(0.5,0.5,0.5,0.5),"cm"),
        legend.position = "top",
        legend.spacing.x = unit(0.5,"cm"),
        text = element_text(family = "Arial")))


ggsave(f8b, file = "../../figures/Figure8B_landscape_metrics.png", dpi = 300) # adjust dpi accordingly
(arr <- ggarrange(f8b,f8a,nrow=2,ncol=1,labels=c("A","B"), common.legend = T))
Warning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font databaseWarning: font family 'Arial' not found in PostScript font database

ggsave(arr, file="../../figures/Figure8_Landscape_Patch_Metrics.png", dpi=300)
Saving 10 x 10 in image

The 10-m aspen classification identified Xx more patch than reference images:

print("% Difference in number of patches across the Southern Rockies: ")
[1] "% Difference in number of patches across the Southern Rockies: "
a <- df %>% filter(region=="SRME",metric=="Number of Patches",source=="Aspen10m")
b <- df %>% filter(region=="SRME",metric=="Number of Patches",source=="LFEVT")
((b$value-a$value)/b$value)*100
[1] -135.4679
print("% Difference in number of patches across the White River NF: ")
[1] "% Difference in number of patches across the White River NF: "
a <- df %>% filter(region=="WRNF",metric=="Number of Patches",source=="Aspen10m")
b <- df %>% filter(region=="WRNF",metric=="Number of Patches",source=="LFEVT")
((b$value-a$value)/b$value)*100
[1] -282.3586

Supplemental

Figure S1: Phenology across the SRME study region


# Spatial map(s) of key phenological metrics

breaks_dates <- as.Date(c("2020-01-01", "2021-01-01", "2022-01-01"))
breaks_numeric <- as.numeric(breaks_dates)  # Convert to numeric (days since 1970-01-01, by default)
labels_dates <- format(breaks_dates, "%Y-%m-%d")

p1 <- ggplot(data=blocks) +
  geom_sf(aes(fill=Maturity_1)) +
  scale_fill_continuous(low = "lightgreen", high = "darkgreen",
                        labels = function(x) format(as.Date(as.numeric(x), origin="1970-01-01"), "%B %d")) +
  geom_sf(data=srme,fill=NA,color="black",size=2.5) +
  labs(fill="Maturity") +
  guides(fill = guide_colourbar(barwidth = 0.5, barheight = 5.5, ticks=F,
                                 label.position = "right", title.position = "top",
                                 label.theme = element_text(angle = 0, size = 8))) +
  theme_void()

p2 <- ggplot(data=blocks) +
  geom_sf(aes(fill=Dormancy_1)) +
  scale_fill_continuous(low = "#ffffd4", high = "#993404",
                        labels = function(x) format(as.Date(as.numeric(x), origin="1970-01-01"), "%B %d")) +
  geom_sf(data=srme,fill=NA,color="black",size=2.5) +
  labs(fill="Dormancy") +
  guides(fill = guide_colourbar(barwidth = 0.5, barheight = 5.5, ticks=F,
                                 label.position = "right", title.position = "top",
                                 label.theme = element_text(angle = 0, size = 8))) +
  theme_void()

ggarrange(p1,p2)


# # Add a statistics column for legend (could do this for F1 too ...)
# blocks.m <- reshape2::melt(
#   blocks, id.vars = c("id","elevation_mn"), 
#   measure.vars = c("Greenup_1","MidGreenup_1","Maturity_1","Peak_1",
#                    "MidGreendown_1","Senescence_1","Dormancy_1"), variable.name = "metric"
# )

# Add a statistics column for legend (could do this for F1 too ...)
blocks.m <- reshape2::melt(
  blocks, id.vars = c("id","elevation_mn"), 
  measure.vars = c("Greenup_1","Maturity_1","Peak_1","Dormancy_1"), variable.name = "metric"
) %>%
  mutate(metric = as.character(metric),
         metric = as.factor(recode(metric,
                         "Greenup_1" = "Greenup",
                         "Maturity_1" = "Maturity",
                         "Peak_1" = "Peak Greenness",
                         "Dormancy_1" = "Dormancy")))
  
blocks.m$metric <- factor(blocks.m$metric, levels=c('Greenup', 'Maturity', 'Peak Greenness', 'Dormancy'))

head(blocks.m)

# Dot plot of phenology metric by elevation for all blocks

(p5 <- ggplot(data=blocks.m, aes(x=value,y=elevation_mn)) +
  geom_smooth(method="lm",colour="gray40", fill="gray70", size=0.8) +
  geom_point(size=1.2) +
  facet_wrap(~metric, scales="free_x") +
  theme_light(12) +
    labs(x="Date",y="Elevation"))


(p6 <- ggplot(data=blocks.m, aes(x=value,y=elevation_mn,color=metric)) +
  geom_point(size=1.2) +
  theme_light(12))

ROC, F1, MCC:


accmeas <- read_csv('../../data/tabular/mod/results/accmeas_prop.csv')
Rows: 1000 Columns: 18── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (18): model, prSize, bgSize, cutoff, tp, tpr, fn, tn, tnr, fp, fpr, specificity, precision, recall, f1, accuracy, gmean, mcc
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Calculate the averages for each cutoff value across model runs

accmeas.mn <- accmeas %>%
  mutate(cutoff = as.character(cutoff)) %>%
  group_by(cutoff) %>%
  summarise(
    prMn = mean(prSize,na.rm=T), # size of presence data (mean)
    prSd = sd(prSize,na.rm=T), # size of presence data (sd)
    bgMn = mean(bgSize,na.rm=T), # size of background data (mean)
    bgSd = sd(bgSize,na.rm=T), # size of background data (sd)
    fprMn = mean(fpr,na.rm=T), # False Positive Rate (mean)
    fprSd = sd(fpr,na.rm=T),
    tprMn = mean(tpr,na.rm=T), # True Positive Rate (mean)
    tprSd = sd(tpr,na.rm=T),
    precision = mean(precision,na.rm=T), # precision (mean)
    precisionSd = sd(precision,na.rm=T), 
    recall = mean(recall,na.rm=T), # recall (mean)
    recallSd = sd(recall,na.rm=T),
    f1 = mean(f1,na.rm=T), # F1 score (mean)
    f1Sd = sd(f1,na.rm=T),
    gmean = mean(gmean,na.rm=T), # Geometric Mean (mean)
    gmeanSd = sd(gmean,na.rm=T),
    mcc = mean(mcc,na.rm=T), # Matthew's Correlation Coefficient (mean)
    mccSd = sd(mcc,na.rm=T),
    accuracy = mean(accuracy,na.rm=T), # Overall accuracy (mean)
    accuracySd = sd(accuracy,na.rm=T)
  ) %>%
  ungroup() %>%
  mutate(cutoff = round(as.double(cutoff),3))

# Calculate optimum threshold based on F1 statistic

cutoffOptMn = accmeas.mn[which.max(accmeas.mn$f1),]$cutoff
precisionOptF1Mn = accmeas.mn[which.max(accmeas.mn$f1),]$precision
recallOptF1Mn = accmeas.mn[which.max(accmeas.mn$f1),]$recall
fprOptMn = accmeas.mn[which.max(accmeas.mn$f1),]$fprMn
tprOptMn = accmeas.mn[which.max(accmeas.mn$f1),]$tprMn
max_f1 <- max(accmeas.mn$f1, na.rm = TRUE)

# ROC Curve and label the AUC value (approximation)

# Get the AUC approx

# Ensure data is sorted by FPR
accmeas_mn_sorted <- accmeas.mn[order(accmeas.mn$fprMn), ]
# Compute AUC using trapz function in 'pracma'
auc_avg_approx <- trapz(accmeas_mn_sorted$fprMn, accmeas_mn_sorted$tprMn)

fs2a <- ggplot(data=accmeas) +
  geom_line(aes(x=fpr,y=tpr,color=factor(model)),linewidth=0.4) +
  scale_color_viridis_d(option="turbo") +
  geom_line(data=accmeas.mn, aes(x = fprMn, y = tprMn), color = "black", size = 0.8) +  # ROC curve average
  scale_x_continuous(limits=c(0,1)) +  # Set x axis limits from 0 to 1
  scale_y_continuous(limits=c(0,1)) +
  labs(x='False Positive Rate', y='True Positive Rate', tag="A") +
  geom_point(aes(x = fprOptMn, y = tprOptMn),
             color = "red", size = 3, shape = 19) +  # optimal threshold point
  geom_text(aes(x=fprOptMn, y=tprOptMn),
            label = paste0('Optimal threshold: ', round(cutoffOptMn,3),'\nApprox. AUC: ', round(auc_avg_approx,3)),
            nudge_x = 0.38, nudge_y = -0.08, size = 3.5) +
  coord_fixed(ratio = 1) +  # to keep the x and y axes scales same
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "black") +  # diagonal
  theme_light() +
  theme(legend.position = "none",
        axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11))

fs2b <- ggplot(data=accmeas) +
  geom_line(aes(x=cutoff,y=f1,color=factor(model)),linewidth=0.4) +
  scale_color_viridis_d(option="turbo") +
  geom_line(data=accmeas.mn, aes(x=cutoff, y = f1), color = "black", size = 0.8) +  # ROC curve average
  # geom_vline(xintercept=0.424, linetype="dashed", color="black") +
  geom_point(aes(x=cutoffOptMn, y=max_f1),
             color = "red", size = 3, shape = 19) +
  geom_text(aes(x=cutoffOptMn, y=max_f1),
            label = paste0('Optimal threshold: ', round(cutoffOptMn,3)),
            nudge_x = 0.0, nudge_y = 0.08, size = 3.5) +
  scale_x_continuous(limits=c(0,1)) +  # Set x axis limits from 0 to 1
  scale_y_continuous(limits=c(0,1)) +
  labs(x='Threshold', y='F1 Score', tag="B") +
  coord_fixed(ratio = 1) +  # to keep the x and y axes scales same
  theme_light() +
  theme(legend.position = "none",
        axis.title.y = element_text(size=11,face="italic",
                                    margin = unit(c(0,0.4,0,0), "cm")),
        axis.title.x = element_text(size=11,face="italic",
                                    margin = unit(c(0.4,0,0,0), "cm")),
        axis.text = element_text(size=10),
        strip.text.x = element_text(size = 11))

(fs2 <- ggarrange(fs2a,fs2b))

ggsave(fs2, file="../../figures/FigureS2_AUC_F1Max.png", dpi=300)


rm(fs2,fs2a,fs2b,accmeas_mn_sorted,auc_avg_approx)

Table S1: Performance metrics for the optimum cutoff value

Create a pretty table.


library(flextable, quietly = T)

# Get the best F1 score for each model run
accmeas.best <- accmeas %>% 
  mutate(model = as.factor(model)) %>%
  group_by(model) %>% 
  top_n(1,f1) %>%
  ungroup() %>%
  mutate(model_iter = row_number()) %>%
  select(model,prSize,bgSize,tpr,precision,recall,f1,cutoff)

head(accmeas.best,10)

# Fix names and add units
names(accmeas.best) <- c("Model Seed",
                         "# of Presence",
                         "# of Background",
                         "True Positive Rate",
                         "Precision",
                         "Recall",
                         "F1-Score",
                         "Threshold")

# Set font name for table
fontname <- "Times New Roman"

# Create the flextable
(ft1 <- flextable(accmeas.best) %>%
 font(fontname = fontname, part = "all") %>%
 autofit() %>% fit_to_width(6.5))

Model Seed

# of Presence

# of Background

True Positive Rate

Precision

Recall

F1-Score

Threshold

847

2,901

25,590

0.8700448

0.9121793

0.8700448

0.8906140

0.3939394

991

6,270

33,755

0.8848485

0.9202189

0.8848485

0.9021872

0.2727273

343

7,868

31,484

0.9246314

0.9192570

0.9246314

0.9219364

0.4242424

197

8,184

38,450

0.8925953

0.9248006

0.8925953

0.9084126

0.3434343

783

6,175

29,167

0.9206478

0.9243902

0.9206478

0.9225152

0.5353535

9

4,467

17,982

0.9131408

0.9575117

0.9131408

0.9348000

0.6565657

283

1,792

18,536

0.8683036

0.9152941

0.8683036

0.8911798

0.5353535

777

5,743

24,471

0.9254745

0.9311493

0.9254745

0.9283032

0.3131313

588

4,441

25,921

0.9119568

0.9414226

0.9119568

0.9264555

0.5050505

708

5,958

31,742

0.9026519

0.9380778

0.9026519

0.9200240

0.4343434

print(ft1, preview = "docx")

# Write to a CSV
write_csv(accmeas.best, "../../figures/TableS1_Accuracy_MaxF1.csv")

Clean up!

LS0tCnRpdGxlOiAiRmlndXJlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCnJvb3QuZGlyOiAnfi9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9PbmVEcml2ZS1QZXJzb25hbC9tY29vay9hc3Blbi1maXJlJwotLS0KCmBgYHtyIGluY2x1ZGU9Rn0Kc291cmNlKCdzZXR1cC5SJykKYGBgCgojIyBTVFVEWSBBUkVBCgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YT1zcm1lKSArCiAgZ2VvbV9zZihkYXRhPXdybmYsIGZpbGw9ImRhcmtncmV5IikgKwogIGdlb21fc2YoZGF0YT1ibG9ja3MsIGZpbGw9TkEsIGx3ZD0wLjQpICsKICBjb29yZF9zZihjcnM9IkVQU0c6MzI2MTMiKSArCiAgdGhlbWVfbGlnaHQoMTEpCmBgYAoKIyMgRmlndXJlIDE6IFNwZWN0cmFsIFJlc3BvbnNlIGFuZCBQaGVub2xvZ2ljYWwgQ2hhcmFjdGVyaXN0aWNzCgpMb2FkIHRoZSBTZW50aW5lbC0yIGFubnVhbCB0aW1lLXNlcmllcyAoMjAxOSkuIAoKYGBge3J9CgojIFNwZWN0cmFsIFJlc3BvbnNlCnRzIDwtIHJlYWRfY3N2KCcuLi8uLi9kYXRhL3RhYnVsYXIvbW9kL3Jlc3VsdHMvczJtc2lfbDJhX2FzcGVuX1RTeTIwMTkuY3N2JywgCiAgICAgICAgICAgICAgIHNob3dfY29sX3R5cGVzID0gRkFMU0UpICU+JSAKICBzZWxlY3QoLWMoaWQsLmdlbyxUQ0IsVENHLFRDVykpICU+JQogIHJlbmFtZShORFZJNzA1ID0gTkRSRSkKCmhlYWQodHMpCgpgYGAKClRpZHkgdGhlIHRpbWUtc2VyaWVzIGRhdGEuCgoxLiBSZW1vdmUgY2xvdWQtY29udGFtaW5hdGVkIHBpeGVscywKMi4gUGl2b3QgdGhlIHRhYmxlIHRvIGdhdGhlciBiYW5kcywKMy4gR2VuZXJhdGUgbWVhbi9tZWRpYW4vc3RkZXYgb2YgcmVmbGVjdGFuY2UgYnkgaW1hZ2UgZGF0ZSwKNC4gQ2FsY3VsYXRlIHdlZWtseSBzdW1tYXJpZXMKCmBgYHtyfQoKIyBGaWx0ZXIgb3V0IGNsb3VkLWNvbnRhbWluYXRlZCBwaXhlbHMgYmFzZWQgb24gdGhlIENsb3VkIFNjb3JlICsgdmFsdWVzCiMgUmVmZXJlbmNlOiAgaHR0cHM6Ly9tZWRpdW0uY29tL2dvb2dsZS1lYXJ0aC9hbGwtY2xlYXItd2l0aC1jbG91ZC1zY29yZS1iZDZlZTJlMjIzNWUKCnRzcCA8LSB0cyAlPiUKICBmaWx0ZXIoCiAgICBjcyA+PSAwLjgsICMgPj0gQ2xvdWQgU2NvcmUgKyBjb250YW1pbmF0aW9uCiAgKSAlPiUKICAjIGFkZCB0aGUgbW9udGggYXMgYSBjb2x1bW4KICBtdXRhdGUobW9udGggPSBtb250aChpbWFnZV9kYXRlLCBsYWJlbD1UUlVFLCBhYmJyPVRSVUUpKSAlPiUKICAjIEZpbHRlciBudWxsIHZhbHVlcwogIGZpbHRlcihjb21wbGV0ZS5jYXNlcyguKSkKCiMgVGVzdCB1c2luZyBzb21lIHZpc3VhbGl6YXRpb25zCgojIE5EVkk3MDUgc2NhdHRlciBwbG90IHdpdGggdHJlbmQgbGluZQp0c3AgJT4lCiAgZ3JvdXBfYnkoaW1hZ2VfZGF0ZSkgJT4lCiAgc3VtbWFyaXplKGJhbmQgPSBtZWRpYW4oTkRWSTcwNSkpICU+JQogIGdncGxvdChhZXMoeD1pbWFnZV9kYXRlLHk9YmFuZCkpICsKICBnZW9tX3BvaW50KHNpemU9MC44KSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSJsb2VzcyIpICsKICBsYWJzKHg9IkltYWdlIERhdGUiLCB5PSJORFJFIikgKwogIHRoZW1lX2J3KDExKQoKIyBORFZJNzA1IGJveCBwbG90CmdncGxvdChkYXRhPXRzcCwgYWVzKHg9TkRWSTcwNSwgeT1tb250aCkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgY29vcmRfZmxpcCgpICsKICBsYWJzKHk9IkltYWdlIERhdGUiLCB4PSJORFJFIikgKwogIHRoZW1lX2J3KDExKQoKcm0odHMpCgpgYGAKCk5vdyBwaXZvdCB0aGUgdGFibGUgbG9uZ2VyOgoKYGBge3J9Cgp0c3BfIDwtIHRzcCAlPiUKICBzZWxlY3QoYyhzdGFydHNfd2l0aCgiQiIpLGltYWdlX2RhdGUsCiAgICAgICAgICAgU0xBVkksTkRWSTcwNSxORE1JLENobFJFLElSRUNJLE1DQVJJKSkgJT4lCiAgZ3JvdXBfYnkoaW1hZ2VfZGF0ZSkgJT4lCiAgc3VtbWFyaXplX2FsbChsaXN0KG1lZGlhbikpICU+JQogIHBpdm90X2xvbmdlcigKICAgIGNvbHMgPSAtYyhpbWFnZV9kYXRlKSwKICAgIG5hbWVzX3RvPSJiYW5kIiwKICAgIHZhbHVlc190bz0icmVmbGVjdGFuY2UiCiAgKSAlPiUKICAjIGFkZCBhIG1vbnRoIGFuZCB3ZWVrIGxhYmVsCiAgbXV0YXRlKAogICAgbW9udGggPSBtb250aChpbWFnZV9kYXRlLCBsYWJlbD1UUlVFLCBhYmJyPVRSVUUpLAogICAgd2VlayA9IGlzb3dlZWsoaW1hZ2VfZGF0ZSksCiAgICBiaXdlZWsgPSBjdXQuRGF0ZShpbWFnZV9kYXRlLCBicmVha3M9IjIgd2VlayIsIGxhYmVscz1GQUxTRSkKICApCgpoZWFkKHRzcF8sIG49MTkpCgpybSh0c3ApCmdjKCkKCmBgYAoKQnJlYWtwb2ludCBhbmFseXNpcyB0byBpZGVudGlmeSBzaWduaWZpY2FudCBzaGlmdCBpbiBzcGVjdHJhbCBzaWduYXR1cmUgdGhyb3VnaG91dCB0aGUgeWVhciBiYXNlZCBvbiBORFJFOgoKYGBge3J9CgojIElzb2xhdGUgb25lIG9mIHRoZSB2ZWdldGF0aW9uIGluZGljZXMgKE5vcm1hbGl6ZWQgRGlmZmVyZW5jZSBSZWQtZWRnZSBJbmRleCkKZGYgPC0gdHNwXyAlPiUgCiAgZmlsdGVyKGJhbmQgPT0gIk5EVkk3MDUiKSAlPiUgCiAgc2VsZWN0KGltYWdlX2RhdGUsd2VlayxyZWZsZWN0YW5jZSkgJT4lCiAgZ3JvdXBfYnkoaW1hZ2VfZGF0ZSx3ZWVrKSAlPiUKICBzdW1tYXJpemUocmVmbGVjdGFuY2UgPSBtZWRpYW4ocmVmbGVjdGFuY2UpKSAlPiUKICB1bmdyb3VwKCkKCiMgQ3JlYXRlIGEgd2Vla2x5IHRpbWUtc2VyaWVzIG9iamVjdApkZi50cyA8LSB0cyhkZiRyZWZsZWN0YW5jZSwgZnJlcXVlbmN5ID0gNTIpCgojIEltcGxlbWVudCB0aGUgY2hhbmdlcG9pbnQgYW5hbHlzaXMgYmFzZWQgb24gTkRSRQpyZXN1bHQgPC0gY2hhbmdlcG9pbnQ6OmNwdC5tZWFudmFyKGRmLnRzLCBtZXRob2Q9IlBFTFQiKQpicC5pbmQgPC0gY2hhbmdlcG9pbnQ6OmNwdHMocmVzdWx0KQoKIyBNYXAgYnJlYWtwb2ludHMgdG8gZGF0ZXMKcHJpbnQoIkJyZWFrcG9pbnQgd2Vla3M6IikKYnAuZGF0ZXMgPC0gZGYkd2Vla1ticC5pbmRdCmJwLmRhdGVzCgojIFBsb3QgdGhlIHJlc3VsdApwbG90KHJlc3VsdCkKCnJtKGRmLCBkZi50cywgcmVzdWx0LCBicC5pbmQsIGJwLmRhdGVzKQoKYGBgCgpQZXJmb3JtIHRoZSBhbmFseXNpcyBieSBzcGF0aWFsIGJsb2NrIGdyaWQgdG8gZ2V0IGFuIGlkZWEgb2YgdGhlIHZhcmlhdGlvbiBpbiB0aW1lLXNlcmllcyBicmVha3BvaW50cy4KCmBgYHtyfQpyZXN1bHRzIDwtIGxpc3QoKQoKZm9yKGJsb2NrIGluIHVuaXF1ZShkZiRibG9ja19pZCkpIHsKICAgICMgU3Vic2V0IGRhdGEgZm9yIHRoZSBibG9jawogICAgYmxvY2tfZGF0YSA8LSBkZiAlPiUgZmlsdGVyKGJsb2NrX2lkID09IGJsb2NrKQoKICAgICMgQ3JlYXRlIHRpbWUgc2VyaWVzIChhc3N1bWluZyB3ZWVrbHkgYWdncmVnYXRpb24pCiAgICBibG9ja190cyA8LSB0cyhibG9ja19kYXRhJHJlZmxlY3RhbmNlLCBmcmVxdWVuY3k9NTIpCgogICAgIyBQZXJmb3JtIGJyZWFrcG9pbnQgYW5hbHlzaXMKICAgIGJsb2NrX3Jlc3VsdCA8LSBjaGFuZ2Vwb2ludDo6Y3B0Lm1lYW52YXIoYmxvY2tfdHMsIG1ldGhvZD0iUEVMVCIpCgogICAgIyBTdG9yZSByZXN1bHRzIHdpdGggYmxvY2sgaWRlbnRpZmllcgogICAgcmVzdWx0c1tbYmxvY2tdXSA8LSBjaGFuZ2Vwb2ludDo6Y3B0cyhibG9ja19yZXN1bHQpCn0KCiMgQ3JlYXRlIGFuIGVtcHR5IGRhdGEgZnJhbWUgdG8gc3RvcmUgYnJlYWtwb2ludCBpbmZvcm1hdGlvbgpicmVha3BvaW50X2RhdGEgPC0gZGF0YS5mcmFtZShibG9ja19pZCA9IGNoYXJhY3RlcigpLCBicmVha3BvaW50X3dlZWsgPSBpbnRlZ2VyKCkpCgojIExvb3AgdGhyb3VnaCBlYWNoIGJsb2NrIGFuZCBhZGQgaXRzIGJyZWFrcG9pbnRzIHRvIHRoZSBkYXRhIGZyYW1lCmZvcihibG9jayBpbiBuYW1lcyhyZXN1bHRzKSkgewogICAgYmxvY2tfYnJlYWtwb2ludHMgPC0gcmVzdWx0c1tbYmxvY2tdXQogICAgYmxvY2tfZGF0YSA8LSBkYXRhLmZyYW1lKGJsb2NrX2lkID0gcmVwKGJsb2NrLCBsZW5ndGgoYmxvY2tfYnJlYWtwb2ludHMpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtwb2ludF93ZWVrID0gYmxvY2tfYnJlYWtwb2ludHMpCiAgICBicmVha3BvaW50X2RhdGEgPC0gcmJpbmQoYnJlYWtwb2ludF9kYXRhLCBibG9ja19kYXRhKQp9CgojIENvbnZlcnQgdGhlICdicmVha3BvaW50X3dlZWsnIHRvIGFjdHVhbCBkYXRlcwojIFRoaXMgY29udmVyc2lvbiBkZXBlbmRzIG9uIHlvdXIgc3BlY2lmaWMgZGF0ZSBhcnJhbmdlbWVudC4gSGVyZSdzIGEgYmFzaWMgZXhhbXBsZToKc3RhcnRfZGF0ZSA8LSBhcy5EYXRlKCIyMDE5LTAxLTAyIikgICMgUmVwbGFjZSB3aXRoIHlvdXIgZGF0YXNldCdzIHN0YXJ0IGRhdGUKYnJlYWtwb2ludF9kYXRhJGJyZWFrcG9pbnRfZGF0ZSA8LSBzdGFydF9kYXRlICsgKGJyZWFrcG9pbnRfZGF0YSRicmVha3BvaW50X3dlZWsgLSAxKSAqIDcKCmdncGxvdChicmVha3BvaW50X2RhdGEsIGFlcyh4ID0gYnJlYWtwb2ludF9kYXRlLCB5ID0gYmxvY2tfaWQsIGdyb3VwID0gYmxvY2tfaWQpKSArCiAgICBnZW9tX2xpbmUoKSArICAjIE9wdGlvbmFsbHkgY29ubmVjdCBicmVha3BvaW50cyBpbiBlYWNoIGJsb2NrCiAgICBnZW9tX3BvaW50KHNpemUgPSAzKSArICAjIEJyZWFrcG9pbnQgbWFya2VycwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIGxhYnMoeCA9ICJEYXRlIiwgeSA9ICJTcGF0aWFsIEJsb2NrIikgKwogICAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLCAgIyBSZW1vdmVzIGhvcml6b250YWwgZ3JpZCBsaW5lcwogICAgICAgICAgcGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKClBsb3Qgb2YgbWVkaWFuIGRhaWx5IG9ic2VydmF0aW9uczoKCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTl9CgojIFJlb3JkZXIgYmFuZCBmYWN0b3JzCnRzcF8kYmFuZCA8LSBmYWN0b3IodHNwXyRiYW5kLCAKICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkIyIiwiQjMiLCJCNCIsIkI1IiwiQjYiLCJCNyIsIkI4IiwiQjhBIiwiQjExIiwiQjEyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNobFJFIiwiSVJFQ0kiLCJORE1JIiwiTkRWSTcwNSIsIlNMQVZJIiwiTUNBUkkiKSkKCnRzcF8gJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3QoYmFuZCwiQiIpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9aW1hZ2VfZGF0ZSx5PXJlZmxlY3RhbmNlICxjb2xvcj1mYWN0b3IoYmFuZCkpKSArCiAgZ2VvbV9saW5lKGxpbmV3aWR0aD0wLjQpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uPSJ0dXJibyIpICsKICBsYWJzKHg9IkFjcXVpc2l0aW9uIERhdGUiLHk9IlJlZmxlY3RhbmNlIixjb2xvcj0iUzIgTVNJIEJhbmQiKSArCiAgdGhlbWVfYncoMTQpICsKICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMCwwLjQsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLjQsMCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTExKSkKYGBgCgojIEZpZ3VyZSAzOiBTcGVjdHJhbCBSZXNwb25zZSBvZiBBc3BlbiBGb3Jlc3QgUHJlc2VuY2UgRGF0YQoKR2F0aGVyIHRoZSBzZWFzb25hbCBjb21wb3NpdGUgc3RhcnQgYW5kIGVuZCBkYXRlczoKCmBgYHtyfQojIFN1bW1lcgooc3RhcnRfc3VtbWVyIDwtIHdlZWsoYXMuRGF0ZSgiMjAxOS0wNi0wNiIpKSkKKGVuZF9zdW1tZXIgPC0gd2Vlayhhcy5EYXRlKCIyMDE5LTA4LTI1IikpKQojIEF1dHVtbgooc3RhcnRfYXV0dW1uIDwtIHdlZWsoYXMuRGF0ZSgiMjAxOS0wOS0yNyIpKSkKKGVuZF9hdXR1bW4gPC0gd2Vlayhhcy5EYXRlKCIyMDE5LTExLTAxIikpKQpgYGAKCkdlbmVyYXRlIHRoZSB3ZWVrbHkgbWVkaWFuIHJlZmxlY3RhbmNlLgoKYGBge3Igd2FybmluZz1GLCBtZXNzYWdlPUYsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTl9CgojIExpc3QgZm9yIGJhbmQgY29sb3JzCgpiYW5kX2NvbG9ycyA8LSBjKAogICJCMiIgID0gIiMxRTkwRkYiLCAgIyBCbHVlOiBEb2RnZXIgQmx1ZQogICJCMyIgID0gIiMwMEZGN0YiLCAgIyBHcmVlbjogU3ByaW5nIEdyZWVuCiAgIkI0IiAgPSAiI0ZGMDAwMCIsICAjIFJlZDogUHVyZSBSZWQKICAiQjUiICA9ICIjRkZBMDdBIiwgICMgUmVkIEVkZ2U6IExpZ2h0IFNhbG1vbgogICJCNiIgID0gIiNGRjhDMDAiLCAgIyBOSVI6IERhcmsgT3JhbmdlCiAgIkI3IiAgPSAiI0QyNjkxRSIsICAjIE5JUjogQ2hvY29sYXRlCiAgIkI4IiAgPSAiI0ZGRDcwMCIsICAjIE5JUjogR29sZAogICJCOEEiID0gIiNCODg2MEIiLCAgIyBOSVI6IERhcmsgR29sZGVucm9kCiAgIkIxMSIgPSAiIzAwOEI4QiIsICAjIFNXSVI6IERhcmsgQ3lhbiAoZGFyayBzaGFkZSBvZiB0ZWFsKQogICJCMTIiID0gIiM4MDAwODAiICAgIyBTV0lSOiBQdXJwbGUKKQoKCiMgR3JvdXAgYnkgd2VlaywgY2FsY3VsYXRlIHRoZSBtZWFuLCBwbG90CgpmM2EgPC0gdHNwXyAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChiYW5kLCJCIikpICU+JQogIGdyb3VwX2J5KGJhbmQsd2VlaykgJT4lCiAgc3VtbWFyaXplKHJlZmwgPSBtZWFuKHJlZmxlY3RhbmNlKSkgJT4lCiAgZ2dwbG90KGFlcyh4PXdlZWsseT1yZWZsLGNvbG9yPWZhY3RvcihiYW5kKSkpICsKICBnZW9tX2xpbmUobGluZXdpZHRoPTAuNikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBiYW5kX2NvbG9ycykgKwogIGxhYnMoeD0iQWNxdWlzaXRpb24gV2VlayIseT0iUmVmbGVjdGFuY2UiLGNvbG9yPU5VTEwpICsKICB0aGVtZV9idygxMSkgKwogIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLDAuNCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAuNCwwLDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSBjKDAuMiwwLjk1KSwKICAgICAgICBsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiLAogICAgICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGw9IndoaXRlIiwgY29sb3I9ImJsYWNrIiksCiAgICAgICAgcGxvdC5tYXJnaW4gPSB1bml0KGMoMS4yLDAuNSwxLjIsMC41KSwgJ2xpbmVzJyksCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiQXJpYWwiKSkgKwogIGFubm90YXRlKCdyZWN0JywgeG1pbj1zdGFydF9zdW1tZXIsIHhtYXg9ZW5kX3N1bW1lciwgeW1pbj0wLCB5bWF4PTYwMDAsIGFscGhhPS4zLCBmaWxsPSdncmF5JykgKwogIGFubm90YXRlKCdyZWN0JywgeG1pbj1zdGFydF9hdXR1bW4sIHhtYXg9ZW5kX2F1dHVtbiwgeW1pbj0wLCB5bWF4PTYwMDAsIGFscGhhPS4zLCBmaWxsPSdncmF5JykgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1zdGFydF9zdW1tZXIsIGxpbmV0eXBlPSJkb3R0ZWQiLCBsd2Q9MC44KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PWVuZF9zdW1tZXIsIGxpbmV0eXBlPSJkb3R0ZWQiLCBsd2Q9MC44KSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PXN0YXJ0X2F1dHVtbiwgbGluZXR5cGU9ImRvdHRlZCIsIGx3ZD0wLjgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9ZW5kX2F1dHVtbiwgbGluZXR5cGU9ImRvdHRlZCIsIGx3ZD0wLjgpCgpmM2EKYGBgCgpGaWd1cmUgMUI6IFRpbWUtc2VyaWVzIGNoYXJ0cyBvZiB0aGUgc3BlY3RyYWwgaW5kaWNlczoKCmBgYHtyIG1lc3NhZ2U9Riwgd2FybmluZz1GfQoKIyBBbGwgYmFuZHMKCiMgQ2FsY3VsYXRlIHRoZSBtYXhpbXVtIHJlZmxlY3RhbmNlIGZvciBlYWNoIGJhbmQKbWF4X3JlZmxfYmFuZCA8LSB0c3BfICU+JQogIGdyb3VwX2J5KGJhbmQpICU+JQogIHN1bW1hcml6ZShtYXhfcmVmbCA9IG1heChyZWZsZWN0YW5jZSkpCgojIE5vdyBjcmVhdGUgeW91ciBwbG90CnRzcF8gJT4lCiAgZ3JvdXBfYnkoYmFuZCwgd2VlaykgJT4lCiAgc3VtbWFyaXplKHJlZmwgPSBtZWFuKHJlZmxlY3RhbmNlKSwgLmdyb3VwcyA9ICdkcm9wJykgJT4lCiAgZ2dwbG90KGFlcyh4ID0gd2VlaywgeSA9IHJlZmwsIGdyb3VwID0gMSkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBtYXhfcmVmbF9iYW5kLCBhZXMoeG1pbj1zdGFydF9zdW1tZXIsIHhtYXg9ZW5kX3N1bW1lciwgeW1pbj0wLCB5bWF4PW1heF9yZWZsKSwgYWxwaGE9LjMsIGZpbGw9J2dyYXknLAogICAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBtYXhfcmVmbF9iYW5kLCBhZXMoeG1pbj1zdGFydF9hdXR1bW4sIHhtYXg9ZW5kX2F1dHVtbiwgeW1pbj0wLCB5bWF4PW1heF9yZWZsKSwgYWxwaGE9LjMsIGZpbGw9J2dyYXknLAogICAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gc3RhcnRfc3VtbWVyLCBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBsd2QgPSAwLjgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBlbmRfc3VtbWVyLCBsaW5ldHlwZSA9ICJkb3R0ZWQiLCBsd2QgPSAwLjgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBzdGFydF9hdXR1bW4sIGxpbmV0eXBlID0gImRvdHRlZCIsIGx3ZCA9IDAuOCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGVuZF9hdXR1bW4sIGxpbmV0eXBlID0gImRvdHRlZCIsIGx3ZCA9IDAuOCkgKwogIGZhY2V0X3dyYXAoLiB+IGJhbmQsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgsIGZhY2UgPSAiaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAsIDAuNCwgMCwgMCksICJjbSIpKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgsIGZhY2UgPSAiaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAuNCwgMCwgMCwgMCksICJjbSIpKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpCgojIFNwZWN0cmFsIGluZGljZXMKCm1heF9yZWZsX2JhbmRfdmkgPC0gbWF4X3JlZmxfYmFuZCAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3QoYmFuZCwiQiIpKQoKZjNiIDwtIHRzcF8gJT4lCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KGJhbmQsIkIiKSkgJT4lCiAgZ3JvdXBfYnkoYmFuZCx3ZWVrKSAlPiUKICBzdW1tYXJpemUocmVmbCA9IG1lYW4ocmVmbGVjdGFuY2UpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9d2VlaywgeT1yZWZsLCBncm91cD0xKSkgKwogIGdlb21fbGluZShsaW5ld2lkdGg9MC42KSArCiAgbGFicyh4PSJBY3F1aXNpdGlvbiBXZWVrIix5PSIiKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBtYXhfcmVmbF9iYW5kX3ZpLCBhZXMoeG1pbiA9IHN0YXJ0X3N1bW1lciwgeG1heCA9IGVuZF9zdW1tZXIsIHltaW4gPSAwLCB5bWF4ID0gbWF4X3JlZmwpLCBhbHBoYSA9IC4zLCBmaWxsID0gJ2dyYXknLAogICAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgZ2VvbV9yZWN0KGRhdGEgPSBtYXhfcmVmbF9iYW5kX3ZpLCBhZXMoeG1pbiA9IHN0YXJ0X2F1dHVtbiwgeG1heCA9IGVuZF9hdXR1bW4sIHltaW4gPSAwLCB5bWF4ID0gbWF4X3JlZmwpLCBhbHBoYSA9IC4zLCBmaWxsID0gJ2dyYXknLAogICAgICAgICAgICBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PXN0YXJ0X3N1bW1lciwgbGluZXR5cGU9ImRvdHRlZCIsIGx3ZD0wLjgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9ZW5kX3N1bW1lciwgbGluZXR5cGU9ImRvdHRlZCIsIGx3ZD0wLjgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9c3RhcnRfYXV0dW1uLCBsaW5ldHlwZT0iZG90dGVkIiwgbHdkPTAuOCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1lbmRfYXV0dW1uLCBsaW5ldHlwZT0iZG90dGVkIiwgbHdkPTAuOCkgKwogIGZhY2V0X3dyYXAoLiB+IGJhbmQsIHNjYWxlcyA9ICJmcmVlIikgKwogIHRoZW1lX2xpZ2h0KDExKSArCiAgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAsMC40LDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMC40LDAsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDExKSwKICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygxLjIsMC41LDEuMiwwLjUpLCAnbGluZXMnKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJBcmlhbCIpKQpmM2IKCnJtKG1heF9yZWZsX2JhbmQsbWF4X3JlZmxfYmFuZF92aSkKCmBgYAoKRmlndXJlIDNDOiBCb3hwbG90IG9mIHNlYXNvbmFsIGNvbXBvc2l0ZXM6CgpgYGB7cn0KCmNvbHMgPC0gYygKICAiU3VtbWVyIiA9ICIjODdDRUVCIiwgCiAgIkF1dHVtbiIgPSAiI0RBQTUyMCIKKQoKc2Vhc29uYWwgPC0gdHNwXyAlPiUKICBtdXRhdGUoc2Vhc29uID0gaWZfZWxzZSh3ZWVrPj0yMiZ3ZWVrPD0zNywiU3VtbWVyIiwibmEiKSwKICAgICAgICAgc2Vhc29uID0gaWZfZWxzZSh3ZWVrPj0zNyZ3ZWVrPD00OCwiQXV0dW1uIixzZWFzb24pKSAlPiUKICBmaWx0ZXIoc2Vhc29uIT0ibmEiKQoKc2Vhc29uYWwkc2Vhc29uIDwtIGZhY3RvcihzZWFzb25hbCRzZWFzb24sIGxldmVscyA9IGMoIlN1bW1lciIsIkF1dHVtbiIpKQogIApmM2MgPC0gc2Vhc29uYWwgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3QoYmFuZCwiQiIpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9YmFuZCx5PXJlZmxlY3RhbmNlLGZpbGw9c2Vhc29uKSkgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNpemUgPSAwLjUsIG91dGxpZXIuY29sb3IgPSAiZ3JleTMwIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbHMpICsKICB0aGVtZV9idygxMikgKwogIGxhYnMoeD0iU2VudGluZWwtMiBNU0kgQmFuZCIseT0iUmVmbGVjdGFuY2UiLGZpbGw9IlNlYXNvbjogIikgKwogIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLDAuNCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAuNCwwLDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgIHBsb3QubWFyZ2luID0gdW5pdChjKDEuMiwwLjUsMS4yLDAuNSksICdsaW5lcycpLAogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkFyaWFsIikpCmYzYwoKYGBgCgpGaWd1cmUgMUQ6IFNlYXNvbmFsIGRpZmZlcmVuY2VzIGluIHRoZSBzcGVjdHJhbCBpbmRpY2VzCgpgYGB7cn0KCmYzZCA8LSBzZWFzb25hbCAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3QoYmFuZCwiQiIpKSAlPiUKICBnZ3Bsb3QoYWVzKHk9cmVmbGVjdGFuY2UpKSArCiAgZ2VvbV9ib3hwbG90KGFlcyhmaWxsPXNlYXNvbikpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xzKSArCiAgZmFjZXRfd3JhcCguIH4gYmFuZCwgc2NhbGVzID0gImZyZWUiKSArCiAgdGhlbWVfYncoMTIpICsKICBsYWJzKGZpbGw9IlNlYXNvbiIseT0iIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMCwwLjQsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLjQsMCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpLAogICAgICAgICMgbGVnZW5kLnBvc2l0aW9uID0gYygwLjg1LDAuMiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTIpLAogICAgICAgIGxlZ2VuZC5zcGFjaW5nLnkgPSB1bml0KDEuNSwgImNtIiksCiAgICAgICAgbGVnZW5kLnNwYWNpbmcueCA9IHVuaXQoMC41LCAnY20nKSwKICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygxLjIsMC41LDEuMiwwLjUpLCAnbGluZXMnKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJBcmlhbCIpKQpmM2QKCmBgYAoKQ29tYmluZSB0aGUgcGxvdHM6CgpgYGB7ciBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9OCwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9CgojIEFycmFuZ2UgaW4gYSBtdWx0aS1wYW5lbCBwbG90CgpmMyA8LSBnZ2FycmFuZ2UoZjNhLGYzYixmM2MsZjNkLCBucm93PTIsbmNvbD0yLCB3aWR0aHM9YygxLCAwLjc1KSwgbGFiZWxzID0gYygiQSIsICJCIiwgIkMiLCAiRCIpKQpmMwoKIyBTYXZlIGl0IG91dAoKZ2dzYXZlKGYzLCBmaWxlID0gIi4uLy4uL2ZpZ3VyZXMvRmlndXJlM19TcGVjdHJhbFJlc3BvbnNlLnBuZyIsCiAgICAgICBkcGkgPSAzMDAsIGJnPSJ3aGl0ZSIpICMgYWRqdXN0IGRwaSBhY2NvcmRpbmdseQoKYGBgCgpWZXJzaW9uIDI6CgpgYGB7ciBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9OH0KCmYzXyA8LSBnZ2FycmFuZ2UoZjNhLGdnYXJyYW5nZShmM2MsZjNkLG5yb3c9MSxuY29sPTIpLAogICAgICAgICAgICAgICAgICBuY29sPTEsd2lkdGhzPWMoMSwgMC43NSksCiAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkEiLCAiQiIsICJDIikpCmYzXwoKZ2dzYXZlKGYzXywgZmlsZSA9ICIuLi8uLi9maWd1cmVzL0ZpZ3VyZTNfU3BlY3RyYWxSZXNwb25zZV92Mi5wbmciLAogICAgICAgZHBpID0gMzAwLCBiZz0id2hpdGUiKSAjIGFkanVzdCBkcGkgYWNjb3JkaW5nbHkKCmBgYAoKQ2xlYW4gdXAgdGhlIHRpbWUtc2VyaWVzIC8gc3BlY3RyYWwgcmVzcG9uc2UgZGF0YToKCmBgYHtyfQpybShmMyxmM2EsZjNiLGYzYyxmM2Qsc2Vhc29uYWwsYmFuZF9jb2xvcnMsY29scyxlbmRfYXV0dW1uLGVuZF9zdW1tZXIsc3RhcnRfYXV0dW1uLHN0YXJ0X3N1bW1lcix0c3BfKQpnYygpCmBgYAoKIyMgRmlndXJlIDQ6IE1vZGVsIGFjY3VyYWN5IGFuZCBmZWF0dXJlIGltcG9ydGFuY2UgcGxvdAoKQmVzdCBQZXJmb3JtaW5nIE1vZGVsOgoKTG9hZCB0aGUgYWNjdXJhY3kgbWV0cmljcyBmb3IgdGhlIFRFU1QgZGF0YToKCmBgYHtyfQpnbGltcHNlKGFjY21lYXMpCmBgYAoKQ2FsY3VsYXRlIHRoZSBtb2RlbCBhdmVyYWdlcyB1c2luZyB0aGUgb3B0aW11bSBjdXRvZmYgdmFsdWU6CgpgYGB7cn0KIyBXaXRob3V0IHRha2luZyB0aGUgYXZlcmFnZQoKcHJpbnQoIn5+fn5+VG90YWwgKG5vdCBhdmVyYWdlaW5nKSBiYXNlZCBvbiBtYXhpbXVtIEYxfn5+fn4iKQoKcGFzdGUwKCJDdXRvZmY6ICIsKGN1dG9mZk9wdEYxID0gYWNjbWVhc1t3aGljaC5tYXgoYWNjbWVhcyRmMSksXSRjdXRvZmYpKQpwYXN0ZTAoIlByZWNpc2lvbjogIiwocHJlY2lzaW9uT3B0RjEgPSBhY2NtZWFzW3doaWNoLm1heChhY2NtZWFzJGYxKSxdJHByZWNpc2lvbikpCnBhc3RlMCgiUmVjYWxsOiAiLChyZWNhbGxPcHRGMSA9IGFjY21lYXNbd2hpY2gubWF4KGFjY21lYXMkZjEpLF0kcmVjYWxsKSkKcGFzdGUwKCJGUFI6ICIsKGZwck9wdCA9IGFjY21lYXNbd2hpY2gubWF4KGFjY21lYXMkZjEpLF0kZnByKSkKcGFzdGUwKCJUUFI6ICIsKHRwck9wdCA9IGFjY21lYXNbd2hpY2gubWF4KGFjY21lYXMkZjEpLF0kdHByKSkKcGFzdGUwKCJNYXggRi1zY29yZTogIiwobWF4X2YxID0gbWF4KGFjY21lYXMkZjEsIG5hLnJtPVRSVUUpKSkKCiMgV2l0aCB0aGUgYWdncmVnYXRpb24vYXZlcmFnaW5nIGFjcm9zcyBtb2RlbHMgYW5kIHRocmVzaG9sZCB2YWx1ZXMKCiMgQ2FsY3VsYXRlIHRoZSBhdmVyYWdlcyBmb3IgZWFjaCBjdXRvZmYgdmFsdWUgYWNyb3NzIG1vZGVsIHJ1bnMKCmFjY21lYXMubW4gPC0gYWNjbWVhcyAlPiUKICBtdXRhdGUoY3V0b2ZmID0gYXMuY2hhcmFjdGVyKGN1dG9mZikpICU+JQogIGdyb3VwX2J5KGN1dG9mZikgJT4lCiAgc3VtbWFyaXNlKAogICAgcHJNbiA9IG1lYW4ocHJTaXplLG5hLnJtPVQpLCAjIHNpemUgb2YgcHJlc2VuY2UgZGF0YSAobWVhbikKICAgIHByU2QgPSBzZChwclNpemUsbmEucm09VCksICMgc2l6ZSBvZiBwcmVzZW5jZSBkYXRhIChzZCkKICAgIGJnTW4gPSBtZWFuKGJnU2l6ZSxuYS5ybT1UKSwgIyBzaXplIG9mIGJhY2tncm91bmQgZGF0YSAobWVhbikKICAgIGJnU2QgPSBzZChiZ1NpemUsbmEucm09VCksICMgc2l6ZSBvZiBiYWNrZ3JvdW5kIGRhdGEgKHNkKQogICAgZnByTW4gPSBtZWFuKGZwcixuYS5ybT1UKSwgIyBGYWxzZSBQb3NpdGl2ZSBSYXRlIChtZWFuKQogICAgZnByU2QgPSBzZChmcHIsbmEucm09VCksCiAgICB0cHJNbiA9IG1lYW4odHByLG5hLnJtPVQpLCAjIFRydWUgUG9zaXRpdmUgUmF0ZSAobWVhbikKICAgIHRwclNkID0gc2QodHByLG5hLnJtPVQpLAogICAgcHJlY2lzaW9uID0gbWVhbihwcmVjaXNpb24sbmEucm09VCksICMgcHJlY2lzaW9uIChtZWFuKQogICAgcHJlY2lzaW9uU2QgPSBzZChwcmVjaXNpb24sbmEucm09VCksIAogICAgcmVjYWxsID0gbWVhbihyZWNhbGwsbmEucm09VCksICMgcmVjYWxsIChtZWFuKQogICAgcmVjYWxsU2QgPSBzZChyZWNhbGwsbmEucm09VCksCiAgICBmMSA9IG1lYW4oZjEsbmEucm09VCksICMgRjEgc2NvcmUgKG1lYW4pCiAgICBmMVNkID0gc2QoZjEsbmEucm09VCksCiAgICBtY2MgPSBtZWFuKG1jYyxuYS5ybT1UKSwgIyBNYXR0aGV3J3MgQ29ycmVsYXRpb24gQ29lZmZpY2llbnQgKG1lYW4pCiAgICBtY2NTZCA9IHNkKG1jYyxuYS5ybT1UKSwKICAgIGFjY3VyYWN5ID0gbWVhbihhY2N1cmFjeSxuYS5ybT1UKSwgIyBPdmVyYWxsIGFjY3VyYWN5IChtZWFuKQogICAgYWNjdXJhY3lTZCA9IHNkKGFjY3VyYWN5LG5hLnJtPVQpCiAgKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKGN1dG9mZiA9IHJvdW5kKGFzLmRvdWJsZShjdXRvZmYpLDMpKQoKIyBDYWxjdWxhdGUgb3B0aW11bSB0aHJlc2hvbGQgYmFzZWQgb24gRjEgc3RhdGlzdGljCgpwcmludCgifn5+fn5Nb2RlbCBhdmVyYWdlcyBhbmQgbWF4aW11bSBGLXNjb3Jlfn5+fn4iKQoKKGN1dG9mZk9wdE1uID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRmMSksXSRjdXRvZmYpCihwcmVjaXNpb25PcHRGMU1uID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRmMSksXSRwcmVjaXNpb24pCihyZWNhbGxPcHRGMU1uID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRmMSksXSRyZWNhbGwpCihmcHJPcHRNbiA9IGFjY21lYXMubW5bd2hpY2gubWF4KGFjY21lYXMubW4kZjEpLF0kZnByTW4pCih0cHJPcHRNbiA9IGFjY21lYXMubW5bd2hpY2gubWF4KGFjY21lYXMubW4kZjEpLF0kdHByTW4pCihtYXhfZjFNbiA8LSBtYXgoYWNjbWVhcy5tbiRmMSwgbmEucm0gPSBUUlVFKSkKCmBgYAoKYGBge3J9CiMgRjEgYmFzZWQKcHJpbnQoIkYxLWJhc2VkIGFjY3VyYWN5IG1ldHJpY3MiKQpwYXN0ZTAoIkYxIFNjb3JlOiAiLChmMU9wdCA9IGFjY21lYXMubW5bd2hpY2gubWF4KGFjY21lYXMubW4kZjEpLF0kZjEpKQpwYXN0ZTAoIk92ZXJhbGwgQWNjdXJhY3k6ICIsKG9hT3B0ID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRmMSksXSRhY2N1cmFjeSkpCnBhc3RlMCgiUHJlY2lzaW9uOiAiLChwcmVjT3B0ID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRmMSksXSRwcmVjaXNpb24pKQpwYXN0ZTAoIlJlY2FsbDogIiwocmVjT3B0ID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRmMSksXSRyZWNhbGwpKQpwYXN0ZTAoIkN1dG9mZjogIiwodGhyZXNoT3B0ID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRmMSksXSRjdXRvZmYpKQoKIyBNYXRoZXdzIENvcnJlbGF0aW9uIENvZWZmaWNpZW50CnByaW50KCItLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0iKQpwcmludCgiTUNDLWJhc2VkIGFjY3VyYWN5IG1ldHJpY3MiKQpwYXN0ZTAoIk1DQzogIiwobWNjT3B0ID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRtY2MpLF0kbWNjKSkKcGFzdGUwKCJPdmVyYWxsIEFjY3VyYWN5OiAiLChvYU9wdG1jYyA9IGFjY21lYXMubW5bd2hpY2gubWF4KGFjY21lYXMubW4kbWNjKSxdJGFjY3VyYWN5KSkKcGFzdGUwKCJQcmVjaXNpb246ICIsKHByZWNPcHRtY2MgPSBhY2NtZWFzLm1uW3doaWNoLm1heChhY2NtZWFzLm1uJG1jYyksXSRwcmVjaXNpb24pKQpwYXN0ZTAoIlJlY2FsbDogIiwocmVjT3B0bWNjID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRtY2MpLF0kcmVjYWxsKSkKcGFzdGUwKCJDdXRvZmY6ICIsKHRocmVzaE9wdG1jYyA9IGFjY21lYXMubW5bd2hpY2gubWF4KGFjY21lYXMubW4kbWNjKSxdJGN1dG9mZikpCmBgYAoKUGxvdCB0aGUgbW9kZWwgYWNjdXJhY3kgcmVzdWx0cyBmb3IgdGhlIG9wdGltdW0gdGhyZXNob2xkLgoKYGBge3J9CgojIEdldCB0aGUgYmVzdCBGMSBzY29yZSBmb3IgZWFjaCBtb2RlbCBydW4KYWNjbWVhcy5iZXN0IDwtIGFjY21lYXMgJT4lIAogIGdyb3VwX2J5KGZhY3Rvcihtb2RlbCkpICU+JSAKICB0b3BfbigxLGYxKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKG1vZGVsX2l0ZXIgPSByb3dfbnVtYmVyKCkpCgpwcmludChwYXN0ZTAoIkN1dG9mZjogTWVhbiA9ICIscm91bmQobWVhbihhY2NtZWFzLmJlc3QkY3V0b2ZmKSwzKSwiOyBTdGFuZGFyZCBEZXZpYXRpb246ICIscm91bmQoc2QoYWNjbWVhcy5iZXN0JGN1dG9mZiksMykpKQpwcmludChwYXN0ZTAoIkYxIFNjb3JlOiBNZWFuID0gIixyb3VuZChtZWFuKGFjY21lYXMuYmVzdCRmMSksMyksIjsgU3RhbmRhcmQgRGV2aWF0aW9uOiAiLHJvdW5kKHNkKGFjY21lYXMuYmVzdCRmMSksMykpKQoKYGBgCgpDaGVjayBvbiB0aGUgb3B0aW11bSB0aHJlc2hvbGQgYnkgc3BhdGlhbCBibG9jazoKCmBgYHtyfQoKYGBgCgpGaWd1cmUgNEE6IE1vZGVsIEFjY3VyYWN5IChGMSBhbmQgT0EpCgpgYGB7ciBtZXNzYWdlPUYsIHdhcm5pbmc9Rn0KCmNvbHMgPC0gYygiRjEgU2NvcmUiPSIjMWY3OGI0IiwiT3ZlcmFsbCBBY2N1cmFjeSI9ImdyYXk3NSIpCgpmNGEgPC0gZ2dwbG90KGRhdGE9YWNjbWVhcy5iZXN0KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTAuOTcyNzYwOTczMDk2ODI0LGxpbmV0eXBlPSJkYXNoZWQiLGNvbG9yPSJncmF5ODUiKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTAuOTEwMzM5NDc3MzU1NjI5LGxpbmV0eXBlPSJkYXNoZWQiLGNvbG9yPSIjMWY3OGI0IikgKwogIGdlb21fbGluZShhZXMoeD1mYWN0b3IobW9kZWxfaXRlcikseT1hY2N1cmFjeSxncm91cD0xKSxjb2xvcj0iZ3JheTY1IikgKwogIGdlb21fbGluZShhZXMoeD1mYWN0b3IobW9kZWxfaXRlcikseT1mMSxncm91cD0xKSxjb2xvcj0iIzFmNzhiNCIpICsKICBnZW9tX3BvaW50KGFlcyh4PWZhY3Rvcihtb2RlbF9pdGVyKSx5PWYxLHNpemU9ZjEpLCBjb2xvcj0iIzFmNzhiNCIpICsKICBnZW9tX3BvaW50KGFlcyh4PWZhY3Rvcihtb2RlbF9pdGVyKSx5PWFjY3VyYWN5LHNpemU9YWNjdXJhY3kpLCBjb2xvcj0iZ3JheTY1IikgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDMsNikpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMC44NSwgMSkpICsKICBsYWJzKHg9Ik1vZGVsIEl0ZXJhdGlvbiIsIHk9IlNjb3JlIiwgdGFnPSJBIikgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDMsIHkgPSAwLjk5LCBsYWJlbCA9IHBhc3RlKCJBdmVyYWdlIE92ZXJhbGwgQWNjdXJhY3k6ICIsIHJvdW5kKG9hT3B0LCAyKSkpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSAyLjYsIHkgPSAwLjg4LCBsYWJlbCA9IHBhc3RlKCJBdmVyYWdlIEYxIFNjb3JlOiAiLCByb3VuZChmMU9wdCwgMikpKSArCiAgdGhlbWVfYncoKSArCiAgZ3VpZGVzKHNpemU9Im5vbmUiKSArCiAgdGhlbWUoCiAgICAgICAgcGxvdC5tYXJnaW4gPSB1bml0KGMoMC4yLDAuMiwwLjIsMC4yKSwgJ2xpbmVzJyksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAsMC40LDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMC40LDAsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDExKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJBcmlhbCIpKQpmNGEKCmdnc2F2ZShmNGEsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmU0QV9CZXN0X01vZGVsX0FjY19GMS5wbmciLAogICAgICAgZHBpID0gMzAwLCBiZz0id2hpdGUiKSAjIGFkanVzdCBkcGkgYWNjb3JkaW5nbHkKCmBgYAoKRmlndXJlIDRCOiBTZW5zaXRpdml0eSBBbmFseXNpcwoKYGBge3J9CgojIExhYmVscyBmb3IgZWFjaCBtb2RlbCBzdWJzZXQKCmxhYmVsc18gPSBjKAogICJ3aW50ZXJfc2FyIiA9ICJNMSIsIAogICJzdW1tZXJfc2FyIiA9ICJNMiIsCiAgInN1bW1lcl93aW50ZXJfc2FyX3RleHQiID0gIk0zIiwKICAic3VtbWVyX3dpbnRlcl9zYXIiID0gIk00IiwKICAiYXV0dW1uX3NwZWN0cmFsIiA9ICJNNSIsCiAgInN1bW1lcl9zcGVjdHJhbCIgPSAiTTYiLAogICJzdW1tZXJfc3BlY3RyYWxfaW5kIiA9ICJNNyIsCiAgInNwZWN0cmFsX3Nhcl9pbmQiID0gIk04IiwKICAic3VtbWVyX2F1dHVtbl9zcGVjdHJhbCIgPSAiTTkiLAogICJzcGVjdHJhbF9zYXIiID0gIk0xMCIsCiAgImZpbmFsIiA9ICJGaW5hbCBNb2RlbCIKKQoKIyBBZGQgaW4gdGhlIGZpbmFsIG1vZGVsIHRvIHRoZSBzZW5zaXRpdml0eSBhbmFseXNpcwoKYWRkIDwtIGFjY21lYXMuYmVzdCAlPiUKICB1bmdyb3VwKCkgJT4lCiAgc2VsZWN0KG1vZGVsLGYxKSAlPiUKICBtdXRhdGUoc3Vic2V0ID0gImZpbmFsIikgJT4lCiAgcmVuYW1lKGYxTWF4ID0gZjEpCmdsaW1wc2UoYWRkKQphY2NtZWFzLnMgPC0gYWNjbWVhcy5zICU+JQogIGJpbmRfcm93cyhhZGQpCnJtKGFkZCkKCnByaW50KHVuaXF1ZShhY2NtZWFzLnMkc3Vic2V0KSkKCiMgR2VuZXJhdGUgdGhlIGJveCBwbG90cyBvZiBGMSBzY29yZQoKZjRiIDwtIGdncGxvdChkYXRhPWFjY21lYXMucywgYWVzKHg9cmVvcmRlcihzdWJzZXQsZjFNYXgpLHk9ZjFNYXgpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPWxhYmVsc18pICsKICBsYWJzKHg9IklucHV0IEZlYXR1cmVzIix5PSJNYXhpbXVtIEYxIFNjb3JlIiwgdGFnPSJCIikgKwogIHRoZW1lX2J3KDExKSArCiAgdGhlbWUoCiAgICAgICAgcGxvdC5tYXJnaW4gPSB1bml0KGMoMC4yLDAuMiwwLjIsMC4yKSwgJ2xpbmVzJyksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAsMC40LDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMC40LDAsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDExKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdD0xKSwKICAgICAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJBcmlhbCIpKQpmNGIKCiMgU2F2ZSBvdXQKCmdnc2F2ZShmNGIsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmU0Ql9Nb2RlbF9TZW5zaXRpdml0eS5wbmciLAogICAgICAgZHBpID0gMzAwLCBiZz0id2hpdGUiKSAjIGFkanVzdCBkcGkgYWNjb3JkaW5nbHkKYGBgCgpDb21iaW5lIHRoZSB0d28gZmlndXJlcyBmb3IgRmlndXJlIDRBQjoKCmBgYHtyIGZpZy53aWR0aD03LjUsIGZpZy5oZWlnaHQ9Nn0KCmY0IDwtIGdnYXJyYW5nZShmNGEsZjRiLG5yb3c9MikKZjQKCmdnc2F2ZShmNCwgZmlsZSA9ICIuLi8uLi9maWd1cmVzL0ZpZ3VyZTRBQl9Nb2RlbF9BY2N1cmFjeV9TZW5zaXRpdml0eS5wbmciLAogICAgICAgd2lkdGg9Ny41LCBoZWlnaHQ9Ny41LCBkcGkgPSAzMDAsIGJnPSJ3aGl0ZSIpICMgYWRqdXN0IGRwaSBhY2NvcmRpbmdseQoKYGBgCgojIyBGaWd1cmUgNjogRmVhdHVyZSBpbXBvcnRhbmNlIGZyb20gdGhlIGJlc3QgbW9kZWwKCmBgYHtyfQpnbGltcHNlKGZ0cl9pbXApCmBgYAoKYGBge3IgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9CgojICMgVGlkeSB0aGUgZGF0YSBmcmFtZQojIGRmLmltcCA8LSBmdHJfaW1wICU+JSAKIyAgIHNlbGVjdCgtYyguZ2VvLGBzeXN0ZW06aW5kZXhgKSkgJT4lCiMgICByZW5hbWUobW9kZWwgPSBzZWVkKSAlPiUKIyAgIG11dGF0ZShtb2RlbCA9IGFzLmZhY3Rvcihtb2RlbCkpICU+JQojICAgIyBTdW1tYXJpemUgYWNyb3NzIHRvIGdyYWIgdGhlIHJlbGF0aXZlIGltcG9ydGFuY2UKIyAgIHJvd3dpc2UoKSAlPiUKIyAgIG11dGF0ZSh0b3RhbCA9IHN1bShjX2Fjcm9zcyhCMTFfYXV0dW1uOlZWX3dpbnRlcl9lbnQpKSkgJT4lCiMgICB1bmdyb3VwKCkgJT4lCiMgICBtdXRhdGUoYWNyb3NzKEIxMV9hdXR1bW46VlZfd2ludGVyX2VudCwgfiAuICogMTAwIC8gdG90YWwpKSAlPiUKIyAgIHNlbGVjdCgtdG90YWwpCiMgZ2xpbXBzZShkZi5pbXApCgojICMgVGlkeSB0aGUgZGF0YSBmcmFtZQojIGRmLmltcCA8LSBmdHJfaW1wICU+JSAKIyAgIHNlbGVjdCgtYyguZ2VvLGBzeXN0ZW06aW5kZXhgKSkgJT4lCiMgICByZW5hbWUobW9kZWwgPSBzZWVkKSAlPiUKIyAgIG11dGF0ZShtb2RlbCA9IGFzLmZhY3Rvcihtb2RlbCkpICU+JQojICAgIyBTdW1tYXJpemUgYWNyb3NzIHRvIGdyYWIgdGhlIHJlbGF0aXZlIGltcG9ydGFuY2UKIyAgIGdyb3VwX2J5KG1vZGVsKSAlPiUKIyAgIG11dGF0ZSh0b3RhbCA9IHN1bShhY3Jvc3MoQjExX2F1dHVtbjpzbG9wZSkpKSAlPiUKIyAgIG11dGF0ZShhY3Jvc3MoQjExX2F1dHVtbjpzbG9wZSwgfiAuIC8gdG90YWwpKSAlPiUKIyAgIHVuZ3JvdXAoKSAlPiUKIyAgIHNlbGVjdCgtdG90YWwpCgojIFRpZHkgdGhlIGRhdGEgZnJhbWUKZGYuaW1wIDwtIGZ0cl9pbXAgJT4lIAogIHNlbGVjdCgtYyguZ2VvLGBzeXN0ZW06aW5kZXhgKSkgJT4lCiAgcmVuYW1lKG1vZGVsID0gc2VlZCkgJT4lCiAgbXV0YXRlKG1vZGVsID0gYXMuZmFjdG9yKG1vZGVsKSkKCmBgYAoKYGBge3J9CgojIFBpdm90IGxvbmdlcgpkZi5pbXAucCA8LSBkZi5pbXAgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAtbW9kZWwpICU+JQogIHJlbmFtZShpbXBvcnRhbmNlID0gdmFsdWUsCiAgICAgICAgIGJhbmQgPSBuYW1lKSAlPiUKICBtdXRhdGUoc2Vhc29uID0gaWZfZWxzZShzdHJfZGV0ZWN0KGJhbmQsIl9hdXR1bW4iKSwgIkF1dHVtbiBTcGVjdHJhbCIsICJTdW1tZXIgU3BlY3RyYWwiKSwKICAgICAgICAgc2Vhc29uID0gaWZfZWxzZShzdHJfZGV0ZWN0KGJhbmQsIl93aW50ZXIiKSwgIldpbnRlciBTQVIiLCBzZWFzb24pLAogICAgICAgICBzZWFzb24gPSBpZl9lbHNlKHN0cl9kZXRlY3QoYmFuZCwiVlZfc3VtbWVyIiksICJTdW1tZXIgU0FSIiwgc2Vhc29uKSwKICAgICAgICAgc2Vhc29uID0gaWZfZWxzZShzdHJfZGV0ZWN0KGJhbmQsIlZIX3N1bW1lciIpLCAiU3VtbWVyIFNBUiIsIHNlYXNvbikpCmdsaW1wc2UoZGYuaW1wLnApCgpgYGAKCmBgYHtyIG1lc3NhZ2U9Rn0KCiMgR3JhYiB0aGUgdG9wIDIwIG92ZXIgYWxsIG1vZGVsIHJ1bnMKCnRvcCA8LSBkZi5pbXAucCAlPiUKICBncm91cF9ieShiYW5kKSAlPiUKICBzdW1tYXJpemUobWVkaWFuID0gbWVkaWFuKGltcG9ydGFuY2UpKSAlPiUKICB1bmdyb3VwKCkKCnRvcCA8LSBoZWFkKGFycmFuZ2UodG9wLGRlc2MobWVkaWFuKSksIG4gPSAyMCkKCiMgQ29sb3IgcGFsZXR0ZQoKY29scyA8LSBjKAogICJTdW1tZXIgU3BlY3RyYWwiID0gIiM4N0NFRUIiLCAKICAiQXV0dW1uIFNwZWN0cmFsIiA9ICIjREFBNTIwIiwKICAiV2ludGVyIFNBUiIgPSAiZ3JheTI5IiwKICAiU3VtbWVyIFNBUiIgPSAiZ3JheTg5IgopCgojIEJveHBsb3QKCiMgJT4lIGZpbHRlcihiYW5kICVpbiUgdG9wJGJhbmQpCmY2IDwtIGdncGxvdChkYXRhPWRmLmltcC5wICU+JSBmaWx0ZXIoYmFuZCAlaW4lIHRvcCRiYW5kKSwgCiAgICAgICAgICAgICAgYWVzKHg9cmVvcmRlcihiYW5kLGltcG9ydGFuY2UpLCBmaWxsPXNlYXNvbikpICsKICBnZW9tX2JveHBsb3QoYWVzKHk9aW1wb3J0YW5jZSksIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoMC41KSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbHMsIAogICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkF1dHVtbiBTcGVjdHJhbCIsIlN1bW1lciBTQVIiLCJTdW1tZXIgU3BlY3RyYWwiLCJXaW50ZXIgU0FSIikpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lX2J3KDExKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZT0xMSksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCB2anVzdD0wLCBzaXplPTExKSwKICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSBjKDEuMiwwLjUpLCAjIExlZnQtYWxpZ25zIHRoZSBsZWdlbmRzCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSksCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiQXJpYWwiKSwKICAgICAgICBwbG90Lm1hcmdpbj11bml0KGMoMC41LDAuNSwwLjUsMC41KSwiY20iKSkgKwogIGxhYnMoeD0iRmVhdHVyZSIsIHk9IkltcG9ydGFuY2UiLCBmaWxsPSJTZWFzb246ICIpCmY2CgpnZ3NhdmUoZjYsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmU2X0ZlYXR1cmVJbXBvcnRhbmNlLnBuZyIsCiAgICAgICBkcGk9MzAwLCBiZz0id2hpdGUiKSAjIGFkanVzdCBkcGkgYWNjb3JkaW5nbHkKCmBgYAoKQ2xlYW4gdXAhCgpgYGB7cn0Kcm0obGlzdCA9IGxzLnN0cihtb2RlID0gJ251bWVyaWMnKSkKcm0oYWNjbWVhcyxhY2NtZWFzLmJlc3QsYWNjbWVhcy5tbixhY2NtZWFzLnMsZGYuaW1wLAogICBkZi5pbXAucCxmNCxmNGEsZjRiLGY2LGZ0cl9pbXAsdG9wLGxhYmVsc18sY29scykKZ2MoKQpgYGAKCiMjIEZpZ3VyZXMgNywgOCAtIENhc2UgU3R1ZHk6IExhbmRzY2FwZSBQYXRjaCBEeW5hbWljcwoKIyBGaWd1cmUgNzogU3BhdGlhbCBBZ3JlZW1lbnQKCmBgYHtyfQoKY29scyA8LSBjKCIjNERBRjRBIiwgIiM5ODRFQTMiLCAiI0ZGN0YwMCIpCmZsYWJzIDwtIGMocHJlYyA9ICJQcmVjaXNpb24iLCByZWMgPSAiUmVjYWxsIiwgZjEgPSAiRjEtc2NvcmUiKQoKIyBSZXNoYXBlIHRoZSBhZ3JlZW1lbnQgdGFibGUgZm9yIHBsb3R0aW5nIGZhY2V0IHdyYXAKCiMgU291dGhlcm4gUm9ja2llcyAoYnkgYmxvY2spCnJlZi5zcm1lX20gPC0gcmVzaGFwZTI6Om1lbHQoCiAgcmVmLnNybWUsIGlkLnZhcnMgPSBjKCJibG9ja3NpemUiLCAic291cmNlIiwgInJlZ2lvbiIsICJncmlkX2lkIiksIAogIG1lYXN1cmUudmFycyA9IGMoInByZWMiLCAicmVjIiwgImYxIiksIHZhcmlhYmxlLm5hbWUgPSAic3RhdGlzdGljIgopICU+JQogIG5hLm9taXQoKQoKIyBXUk5GIGFzIGEgd2hvbGUKcmVmLndybmZfbSA8LSByZXNoYXBlMjo6bWVsdCgKICByZWYud3JuZiwgaWQudmFycyA9IGMoImJsb2Nrc2l6ZSIsICJzb3VyY2UiLCAicmVnaW9uIiksIAogIG1lYXN1cmUudmFycyA9IGMoInByZWMiLCAicmVjIiwgImYxIiksIHZhcmlhYmxlLm5hbWUgPSAic3RhdGlzdGljIgopICU+JQogIG5hLm9taXQoKQoKIyBGYWNldCB3cmFwIHBsb3Qgb2YgdGhlIHN0YXRpc3RpY3MgYnkgYmxvY2sgZ3JvdXAKCmY3IDwtIGdncGxvdChkYXRhPXJlZi5zcm1lX20sIGFlcyh4PWZhY3RvcihibG9ja3NpemUpLCB5PXZhbHVlLCBmaWxsPWZhY3Rvcihzb3VyY2UpKSkgKwogIGdlb21fYm94cGxvdChwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKCkpICsgCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNvbHMsIGxhYmVscz1jKCJMQU5ERklSRSBFVlQiLCJVU0ZTIFRyZWVNYXAiLCJVU0ZTIElUU1AiKSwgbmFtZT0iU291cmNlOiAiKSArCiAgZmFjZXRfd3JhcCh+c3RhdGlzdGljLCBsYWJlbGxlciA9IGxhYmVsbGVyKHN0YXRpc3RpYyA9IGZsYWJzKSkgKwogIHRoZW1lX2xpZ2h0KDExKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICAgICAgbGVnZW5kLmJveCA9ICJ2ZXJ0aWNhbCIsCiAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSBjKDAuNSwwLjUpLCAjIExlZnQtYWxpZ25zIHRoZSBsZWdlbmRzCiAgICAgICAgbGVnZW5kLnNwYWNpbmcueSA9IHVuaXQoLTEwLCAicHQiKSwgIyBBZGp1c3RzIHRoZSBnYXAgYmV0d2VlbiB0aGUgbGVnZW5kcwogICAgICAgIGxlZ2VuZC50ZXh0ID0gZ2d0ZXh0OjplbGVtZW50X21hcmtkb3duKGhhbGlnbiA9IDApLCAjIFRoaXMgYWxpZ25zIGVhY2ggaW5kaXZpZHVhbCBsZWdlbmQncyB0ZXh0IHRvIHRoZSBsZWZ0CiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTEyLGZhY2U9Iml0YWxpYyIpLAogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkFyaWFsIikpICsKICBsYWJzKHg9IkJsb2Nrc2l6ZSIseT0iVmFsdWUiKQoKIyBBZGQgdGhlIFdSTkYgc3RhdGlzdGljcwpmNyA8LSBmNyArCiAgIyBQbG90IHRoZSBXUk5GIHBvaW50cyBvbiB0b3AgYXMgYSBkaWZmZXJlbnQgc2hhcGUKICBnZW9tX3BvaW50KGRhdGE9cmVmLndybmZfbSwgYWVzKHg9ZmFjdG9yKGJsb2Nrc2l6ZSksIHk9dmFsdWUsIHNoYXBlPXJlZ2lvbiksIAogICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuNzUpLCBzaXplID0gMywgY29sb3I9ImJsYWNrIikgKwogIHNjYWxlX3NoYXBlX21hbnVhbChuYW1lPSJTdWJyZWdpb24iLCB2YWx1ZXM9YygiV1JORiIgPSA4KSkgKwogIGd1aWRlcygKICAgIGZpbGw9Z3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcz1saXN0KHNoYXBlPU5BKSksICMgTm8gc2hhcGUgaW4gdGhlIGZpbGwgbGVnZW5kCiAgICBzaGFwZT1ndWlkZV9sZWdlbmQodGl0bGU9IlN1YnJlZ2lvbiIsIG92ZXJyaWRlLmFlcz1saXN0KGNvbG9yPSJibGFjayIpKSAjIFNoYXBlIGxlZ2VuZCBmb3IgU3VicmVnaW9uCiAgKQoKZjcKCiMgQWRkIGEgc3BhdGlhbCBtYXAgb2Ygc3RhdGlzdGljcyBieSBibG9ja3MKYmxvY2tzXyA8LSBibG9ja3MgJT4lCiAgbGVmdF9qb2luKHJlZi5zcm1lLCBieT0iZ3JpZF9pZCIpICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhwcmVjLCByZWMsIGYxKSwgbmFtZXNfdG8gPSAic3RhdGlzdGljIiwgdmFsdWVzX3RvID0gInZhbHVlIikKCmdncGxvdChkYXRhID0gYmxvY2tzXykgKwogIGdlb21fc2YoYWVzKGZpbGwgPSB2YWx1ZSkpICsKICBmYWNldF9ncmlkKHNvdXJjZSB+IHN0YXRpc3RpYykgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJDIikgKyAjIFVzZSBhIGNvbnRpbnVvdXMgY29sb3Igc2NhbGUKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKAogICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIgogICkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfY29sb3VyYmFyKGRpcmVjdGlvbiA9ICJob3Jpem9udGFsIiwgYmFyd2lkdGggPSAxMCwgYmFyaGVpZ2h0ID0gMC42MCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aWNrcz1GLCB0aXRsZS5wb3NpdGlvbiA9ICJsZWZ0IiksCiAgICAgICAgIGxhYmVsLnRoZW1lID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgc2l6ZSA9IDkpKSArCiAgbGFicyhmaWxsPSIiKQoKYGBgCgoKYGBge3J9CgpicmV3ZXIucGFsKG49NSwiU2V0MSIpCgpjb2xzIDwtIGMoIiM0REFGNEEiLCAiIzk4NEVBMyIsICIjRkY3RjAwIikKCmdsaW1wc2UocmVmLmdsb2JhbCkKCiMgIyBHZXQgYW4gYXZlcmFnZSBmMSBzY29yZSB0YWJsZQojIGYxbW4gPC0gcmVmLmdsb2JhbCAlPiUKIyAgIGdyb3VwX2J5CgojIEFkZCBhIHN0YXRpc3RpY3MgY29sdW1uIGZvciBsZWdlbmQgKGNvdWxkIGRvIHRoaXMgZm9yIEYxIHRvbyAuLi4pCnJlZi5nbG9iYWwubSA8LSByZXNoYXBlMjo6bWVsdCgKICByZWYuZ2xvYmFsLCBpZC52YXJzID0gYygiYmxvY2tzaXplIiwgInNvdXJjZSIsICJyZWdpb24iKSwgCiAgbWVhc3VyZS52YXJzID0gYygicHJlYyIsICJyZWMiKSwgdmFyaWFibGUubmFtZSA9ICJzdGF0aXN0aWMiCikKCmdsaW1wc2UocmVmLmdsb2JhbC5tKQoKZjdhIDwtIGdncGxvdChkYXRhPXJlZi5nbG9iYWwubSkgKwogIGdlb21fbGluZShhZXMoeD1ibG9ja3NpemUseT12YWx1ZSxjb2xvcj1mYWN0b3Ioc291cmNlKSwgbGluZXR5cGU9c3RhdGlzdGljKSwgbGluZXdpZHRoPTAuNikgKwogIGdlb21fcG9pbnQoZGF0YT1yZWYuZ2xvYmFsLCBhZXMoeD1ibG9ja3NpemUsIHk9ZjEsIGNvbG9yPXNvdXJjZSkpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPWMoMSwzLDUsNyw5KSkgKwogIHNjYWxlX2xpbmV0eXBlX21hbnVhbCh2YWx1ZXMgPSBjKCJwcmVjIiA9ICJkb3R0ZWQiLCAicmVjIiA9ICJzb2xpZCIpLCBuYW1lPSJTdGF0aXN0aWM6ICIsCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJQcmVjaXNpb24iLCJSZWNhbGwiKSkgKwogIGZhY2V0X3dyYXAofnJlZ2lvbiwgc2NhbGVzID0gImZpeGVkIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9Y29scywgbGFiZWxzPWMoIkxBTkRGSVJFIEVWVCIsIlVTRlMgVHJlZU1hcCIsIlVTRlMgSVRTUCIpLCBuYW1lPSJTb3VyY2U6ICIpICsKICB0aGVtZV9saWdodCgxNCkgKwogIHlsaW0oMCwxKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICAgICAgbGVnZW5kLmJveCA9ICJ2ZXJ0aWNhbCIsCiAgICAgICAgbGVnZW5kLmp1c3RpZmljYXRpb24gPSBjKDAuNSwwLjUpLCAjIExlZnQtYWxpZ25zIHRoZSBsZWdlbmRzCiAgICAgICAgbGVnZW5kLnNwYWNpbmcueSA9IHVuaXQoLTEwLCAicHQiKSwgIyBBZGp1c3RzIHRoZSBnYXAgYmV0d2VlbiB0aGUgbGVnZW5kcwogICAgICAgIGxlZ2VuZC50ZXh0ID0gZ2d0ZXh0OjplbGVtZW50X21hcmtkb3duKGhhbGlnbiA9IDApLCAjIFRoaXMgYWxpZ25zIGVhY2ggaW5kaXZpZHVhbCBsZWdlbmQncyB0ZXh0IHRvIHRoZSBsZWZ0CiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTEyLGZhY2U9Iml0YWxpYyIpLAogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkFyaWFsIikpICsKICBsYWJzKHg9IkJsb2Nrc2l6ZSAocGl4ZWxzKSIseT0iVmFsdWUiKQogIApmN2EKCmdnc2F2ZShmN2EsIGZpbGUgPSAiLi4vLi4vZmlndXJlcy9GaWd1cmU3X0dsb2JhbF9QcmVjUmVjX0FncmVlbWVudC5wbmciLCBkcGk9MzAwKQoKYGBgCgpGb2NhbCBhY2N1cmFjeToKCmBgYHtyIG1lc3NhZ2U9Rn0KCiMgcmVmLmZvY2FsJHJlZmJ1ZGVucyA8LSBsb2coMSArIHJlZi5mb2NhbCR0cCArIHJlZi5mb2NhbCRmbikKIyAKIyBnZ3Bsb3QocmVmLmZvY2FsICU+JSBmaWx0ZXIocmVnaW9uPT0iU1JNRSIpLCBhZXMoeCA9IHByZWMsIHkgPSByZWMsIGNvbG9yID0gcmVmYnVkZW5zKSkgKwojICAgIGdlb21fcG9pbnQoc2l6ZSA9IDIsIGFscGhhID0gMC45KSArCiMgICAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKwojICAgIGZhY2V0X2dyaWQocm93cyA9IHZhcnMoZ2VvZ19zY2FsZSksIGNvbHMgPSB2YXJzKGJsb2Nrc2l6ZSksIHNjYWxlcyA9ICJmaXhlZCIpICsKIyAgICB0aGVtZV9saWdodCgpICsKIyAgICBsYWJzKAojICAgICAgIHRpdGxlID0gJ1NwYXRpYWxseSBleHBsaWNpdCBhY2N1cmFjeTogUHJlY2lzaW9uICh4KSB2cy4gUmVjYWxsICh5KScsCiMgICAgICAgc3VidGl0bGUgPSAnZm9yIG11bHRpcGxlIGFuYWx5dGljYWwgdW5pdHMgYW5kIHNwYXRpYWwgc3VwcG9ydCBsZXZlbHMnLAojICAgICAgIGNvbG9yID0gJ3JlZmJ1ZGVucycsCiMgICAgICAgeCA9ICdQcmVjaXNpb24nLAojICAgICAgIHkgPSAnUmVjYWxsJwojICAgICkgKwojICAgIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygwLCAxKSwgeWxpbSA9IGMoMCwgMSkpICsKIyAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKCmBgYAoKSm9pbiB0aGUgdHdvIHBsb3RzOgoKYGBge3J9CiMgZjcgPC0gZ2dhcnJhbmdlKGdsb2JhbF9wcmVjcmVjLGZvY2FsLnByZWMsZm9jYWwucmVjLG5yb3c9MSxsYWJlbHM9YygiQSIsIkIiLCJDIikpCiMgZjcKIyBnZ3NhdmUoZjcsIGZpbGU9Ii4uLy4uL2ZpZ3VyZXMvRmlndXJlN19HbG9iYWxfRm9jYWxfQWdyZWVtZW50LnBuZyIsIGRwaT0zMDApCmBgYAoKQ2xlYW4gdXAhIAoKYGBge3J9CnJtKHJlZi5nbG9iYWwscmVmLmdsb2JhbC5tLGY3YSxmN2IsZjcscmVmLmZvY2FsKQpnYygpCmBgYAoKIyBGaWd1cmUgODogTGFuZHNjYXBlIFBhdGNoIER5bmFtaWNzCgpgYGB7cn0KdW5pcXVlKGZhY3RvcihwYXRjaF9tZXRyaWNzJHNvdXJjZSkpCnN1bW1hcnkocGF0Y2hfbWV0cmljcykKYGBgCgpDYWxjdWxhdGUgc29tZSBncm91cGVkIHN0YXRpc3RpY3MgKHRvIGNvbXBhcmUgd2l0aCB0aGUgY2xhc3MgbWV0cmljcyByZXN1bHRzKQoKYGBge3IgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTh9CgojIEFsc28gY3JlYXRlIGEgZ3JvdXBlZCBzdW1tYXJ5CihwYXRjaC5zdW1tYXJ5IDwtIHBhdGNoX21ldHJpY3MgJT4lCiAgZ3JvdXBfYnkoc291cmNlLHJlZ2lvbikgJT4lCiAgc3VtbWFyaXplKGFyZWFfbWQgPSBtZWRpYW4oYXJlYSksICMgY29udmVydCB0byBoZWN0YXJlcwogICAgICAgICAgICBhcmVhX21uID0gbWVhbihhcmVhKSwKICAgICAgICAgICAgYXJlYV9zdW0gPSBzdW0oYXJlYSksCiAgICAgICAgICAgIHBlcmltX21kID0gbWVkaWFuKHBlcmltZXRlciksCiAgICAgICAgICAgIHBlcmltX21uID0gbWVhbihwZXJpbWV0ZXIpLAogICAgICAgICAgICBwYXJfbWQgPSBtZWRpYW4ocGVyaW1ldGVyX2FyZWFfcmF0aW8pLAogICAgICAgICAgICBwYXJfbW4gPSBtZWFuKHBlcmltZXRlcl9hcmVhX3JhdGlvKSwKICAgICAgICAgICAgc2lfbWQgPSBtZWRpYW4oc2hhcGVfaW5kZXgpLAogICAgICAgICAgICBzaV9tbiA9IG1lYW4oc2hhcGVfaW5kZXgpKSAlPiUKICB1bmdyb3VwKCkpCgoocGF0Y2gubG9uZyA8LSBwYXRjaC5zdW1tYXJ5ICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhjb250YWlucygiXyIpKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibWV0cmljIiwKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikpCgpwYXRjaC5sb25nJHNvdXJjZSA8LSBmYWN0b3IocGF0Y2gubG9uZyRzb3VyY2UsbGV2ZWxzPWMoJ0FzcGVuMTBtJywgJ0FzcGVuMzBtJywgJ0xGRVZUJywgJ1RyZWVNYXAnLCAnSVRTUCcpKQoKKGdncGxvdChkYXRhID0gcGF0Y2gubG9uZywgYWVzKHg9c291cmNlLCB5PXZhbHVlLCBmaWxsPXJlZ2lvbikpICsKICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgcG9zaXRpb249ImRvZGdlIiwgd2lkdGg9MC43KSArCiAgICBmYWNldF93cmFwKH5tZXRyaWMsIHNjYWxlcz0iZnJlZSIpICsgCiAgICB0aGVtZV9taW5pbWFsKDE0KSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgdGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiQXJpYWwiKSkpCgooZjhzIDwtIGdncGxvdChkYXRhID0gcGF0Y2gubG9uZywgYWVzKHg9c291cmNlLCB5PXZhbHVlLCBmaWxsPXJlZ2lvbikpICsKICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgcG9zaXRpb249ImRvZGdlIiwgd2lkdGg9MC43KSArIAogICAgdGhlbWVfbWluaW1hbCgxNCkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkFyaWFsIikpKQoKYGBgCgpHcmFiIHNvbWUgc3VtbWFyeSBzdGF0aXN0aWNzIG9uIHRoZSBwYXRjaCBkeW5hbWljczoKCmBgYHtyfQoKYSA8LSBwYXRjaC5sb25nICU+JSBmaWx0ZXIocmVnaW9uPT0iU1JNRSIsbWV0cmljPT0iYXJlYV9tbiIsc291cmNlPT0iQXNwZW4xMG0iKQpiIDwtIHBhdGNoLmxvbmcgJT4lIGZpbHRlcihyZWdpb249PSJTUk1FIixtZXRyaWM9PSJhcmVhX21uIixzb3VyY2U9PSJMRkVWVCIpCgpwcmludCgiRGlmZmVyZW5jZSBpbiBtZWFuIHBhdGNoIHNpemUgZm9yIHRoZSBTUk1FOiAiKQooKGIkdmFsdWUtYSR2YWx1ZSkvYiR2YWx1ZSkqMTAwCgphIDwtIHBhdGNoLmxvbmcgJT4lIGZpbHRlcihyZWdpb249PSJXUk5GIixtZXRyaWM9PSJhcmVhX21uIixzb3VyY2U9PSJBc3BlbjEwbSIpCmIgPC0gcGF0Y2gubG9uZyAlPiUgZmlsdGVyKHJlZ2lvbj09IldSTkYiLG1ldHJpYz09ImFyZWFfbW4iLHNvdXJjZT09IkxGRVZUIikKCnByaW50KCJEaWZmZXJlbmNlIGluIG1lYW4gcGF0Y2ggc2l6ZSBmb3IgdGhlIFdSTkY6ICIpCigoYiR2YWx1ZS1hJHZhbHVlKS9iJHZhbHVlKSoxMDAKCnByaW50KCJ+fn5+fn5+fn5+fn5+fn5+IikKCnByaW50KCJEaWZmZXJlbmNlIGluIG1lYW4gcGF0Y2ggc2l6ZSBmb3IgdGhlIFNSTUU6ICIpCigoYiR2YWx1ZS1hJHZhbHVlKS9iJHZhbHVlKSoxMDAKCihjIDwtIHBhdGNoLmxvbmcgJT4lIGZpbHRlcihtZXRyaWM9PSJhcmVhX21kIixzb3VyY2U9PSJBc3BlbjEwbSIpKQooZCA8LSBwYXRjaC5sb25nICU+JSBmaWx0ZXIobWV0cmljPT0iYXJlYV9tZCIsc291cmNlPT0iTEZFVlQiKSkKCnByaW50KCJNZWFuIGFuZCBtZWRpYW4gcGF0Y2ggc2l6ZXM6ICIpCnBhc3RlMCgiQXNwZW4xMG0gbWVhbjogIixhJHZhbHVlKQpwYXN0ZTAoIkxGRVZUIG1lYW46ICIsYiR2YWx1ZSkKcGFzdGUwKCJBc3BlbjEwbSBtZWRpYW46ICIsYyR2YWx1ZSkKcGFzdGUwKCJMRkVWVCBtZWFuOiAiLGQkdmFsdWUpCgoKCnByaW50KCJEaWZmZXJlbmNlIGluIG1lZGlhbiBhcmVhOiAiKQooKGQkdmFsdWUtYyR2YWx1ZSkvZCR2YWx1ZSkqMTAwCgpwcmludCgiTWVhbiBQZXJpbWV0ZXIgYXJlYSByYXRpbzogIikKKGEgPC0gcGF0Y2gubG9uZyAlPiUgZmlsdGVyKG1ldHJpYz09InBhcl9tbiIsc291cmNlPT0iQXNwZW4xMG0iKSkKKGIgPC0gcGF0Y2gubG9uZyAlPiUgZmlsdGVyKG1ldHJpYz09InBhcl9tbiIsc291cmNlPT0iTEZFVlQiKSkKCnByaW50KCJEaWZmZXJlbmNlIGluIFBBUjogIikKKChiJHZhbHVlLWEkdmFsdWUpL2IkdmFsdWUpKjEwMAoKcm0oYSxiLGMsZCkKYGBgCgpCb3hwbG90IGFzIGZhY2V0IHdyYXAgZm9yIHBhdGNoIG1ldHJpY3M6CgpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMH0KCmJyZXdlci5wYWwobj01LCJTZXQxIikKCmNvbHMgPC0gYygiIzAwNWEzMiIsIiNhMWQ5OWIiKQoKKGRmIDwtIHBhdGNoX21ldHJpY3MgJT4lCiAgIyBtdXRhdGUoYXJlYV9oYSA9IGFyZWEqMTAwKSAlPiUKICBzZWxlY3QoLWMoWCxpbmRleCxzaGFwZV9pbmRleCkpICU+JQogIHBpdm90X2xvbmdlcihjb250YWlucyhjKCJhcmVhIiwicGVyaW1ldGVyX2FyZWFfcmF0aW8iKSkpICU+JQogIGFycmFuZ2Uoc291cmNlLCBkZXNjKHZhbHVlKSkgJT4lCiAgbXV0YXRlKG5hbWUgPSBhcy5jaGFyYWN0ZXIobmFtZSksCiAgICAgICAgIG5hbWUgPSByZWNvZGUobmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAiYXJlYSIgPSAiUGF0Y2ggU2l6ZSAoaGEpIiwKICAgICAgICAgICAgICAgICAgICAgICAicGVyaW1ldGVyX2FyZWFfcmF0aW8iID0gIlBlcmltZXRlci9BcmVhIFJhdGlvIikpKQoKZGYkc291cmNlIDwtIGZhY3RvcihkZiRzb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPWMoJ0FzcGVuMTBtJywgJ0FzcGVuMzBtJywgJ0xGRVZUJywgJ1RyZWVNYXAnLCAnSVRTUCcpKQogCihmOGEgPC0gZ2dwbG90KGRhdGE9ZGYsIGFlcyh5ID0gdmFsdWUsIHggPSBmYWN0b3Ioc291cmNlKSwgZmlsbCA9IGZhY3RvcihyZWdpb24pKSkgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNpemUgPSAwLjIpICsKICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnM9ImxvZzEwIixsYWJlbHM9bGFiZWxfbnVtYmVyX3NpKGFjY3VyYWN5ID0gMSkpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y29scykgKwogIGZhY2V0X3dyYXAofm5hbWUsIHNjYWxlcyA9ICJmcmVlX3kiLCBucm93PTEpICsKICBsYWJzKHg9IlxuRGF0YSBTb3VyY2UiLHk9IlZhbHVlIix0aXRsZT0iUGF0Y2ggTWV0cmljcyIsIGZpbGw9IlJlZ2lvbjogIikgKwogIHRoZW1lX2xpZ2h0KDEyKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCBzaXplPTExKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIHZqdXN0PTAuMSwgc2l6ZT0xMSksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTIsIGZhY2U9Iml0YWxpYyIsIHZqdXN0PTEpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTEyLCBmYWNlPSJpdGFsaWMiLCB2anVzdD0tMSksCiAgICAgICAgcGxvdC5tYXJnaW4gPSB1bml0KGMoMC41LDAuNSwwLjUsMC41KSwiY20iKSwKICAgICAgICBsZWdlbmQucG9zaXRpb249InRvcCIsCiAgICAgICAgbGVnZW5kLnNwYWNpbmcueCA9IHVuaXQoMC41LCJjbSIpLAogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkFyaWFsIikpKQoKZ2dzYXZlKGY4YSwgZmlsZSA9ICIuLi8uLi9maWd1cmVzL0ZpZ3VyZThBX3BhdGNoX21ldHJpY3MucG5nIiwgZHBpID0gMzAwKSAjIGFkanVzdCBkcGkgYWNjb3JkaW5nbHkKCmBgYAoKUGxvdCBjbGFzcyBtZXRyaWNzIGFzIGZhY2V0IHdyYXA6CgpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMCwgbWVzc2FnZT1GfQoKIyBDYWxjdWxhdGUgYXJlYSBpbiBzcXVhcmUgbWV0ZXJzCnNybWVfYXJlYV9rbTIgPC0gc3RfYXJlYShzcm1lKSAvIDFlNgp3cm5mX2FyZWFfa20yIDwtIHN0X2FyZWEod3JuZikgLyAxZTYKCihkZiA8LSBjbGFzc19tZXRyaWNzICU+JQogIG11dGF0ZSh0b3RhbF9hcmVhID0gdG90YWxfYXJlYSowLjAxLAogICAgICAgICBwcm9wX2FyZWEgPSBpZl9lbHNlKHJlZ2lvbj09IlNSTUUiLCBhcy5kb3VibGUoKHRvdGFsX2FyZWEvc3JtZV9hcmVhX2ttMioxMDApKSwgMC4wKSwKICAgICAgICAgcHJvcF9hcmVhID0gaWZfZWxzZShyZWdpb249PSJXUk5GIiwgYXMuZG91YmxlKCh0b3RhbF9hcmVhL3dybmZfYXJlYV9rbTIqMTAwKSksIHByb3BfYXJlYSkpICU+JQogIHNlbGVjdCgtYyhYKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKGNvbnRhaW5zKCJfIikpLAogICAgICAgICAgICAgICBuYW1lc190byA9ICJtZXRyaWMiLAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiKSAlPiUKICBtdXRhdGUobWV0cmljID0gYXMuY2hhcmFjdGVyKG1ldHJpYyksCiAgICAgICAgIG1ldHJpYyA9IHJlY29kZShtZXRyaWMsCiAgICAgICAgICAgICAgICAgICAgICAgICAibl9wYXRjaCIgPSAiTnVtYmVyIG9mIFBhdGNoZXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgInBhdGNoX2RlbiIgPSAiUGF0Y2ggRGVuc2l0eSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAidG90YWxfYXJlYSIgPSAiVG90YWwgYXJlYSAoa20yKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAicHJvcF9hcmVhIiA9ICJQcm9wb3J0aW9uIG9mIEFyZWEiKSkpCgpkZiRzb3VyY2UgPC0gZmFjdG9yKGRmJHNvdXJjZSwgbGV2ZWxzPWMoJ0FzcGVuMTBtJywgJ0FzcGVuMzBtJywgJ0xGRVZUJywgJ1RyZWVNYXAnLCAnSVRTUCcpKQpoZWFkKGRmKQoKKGY4YiA8LSBnZ3Bsb3QoZGF0YT1kZiwgYWVzKHk9dmFsdWUsIHg9ZmFjdG9yKHNvdXJjZSksIGZpbGw9ZmFjdG9yKHJlZ2lvbikpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBwb3NpdGlvbj0iZG9kZ2UiLCB3aWR0aD0wLjcpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzPXNjYWxlczo6bGFiZWxfbnVtYmVyX3NpKGFjY3VyYWN5ID0gMSkpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y29scykgKwogIGxhYnMoeD0iIiwgeT0iVmFsdWUiLCB0aXRsZT0iTGFuZHNjYXBlIE1ldHJpY3MiLCBmaWxsPSJSZWdpb246ICIpICsKICB0aGVtZShheGlzLnRpdGxlLnggPSBOVUxMKSArCiAgZmFjZXRfd3JhcCh+bWV0cmljLCBzY2FsZXMgPSAiZnJlZV95IikgKwogIHRoZW1lX2xpZ2h0KDEyKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCBzaXplPTExKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIHZqdXN0PTAuMSwgc2l6ZT0xMSksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTIsIGZhY2U9Iml0YWxpYyIsIHZqdXN0PTAuNSksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemU9MTIsIGZhY2U9Iml0YWxpYyIsIHZqdXN0PS0wLjUpLAogICAgICAgIHBsb3QubWFyZ2luID0gdW5pdChjKDAuNSwwLjUsMC41LDAuNSksImNtIiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICAgICAgbGVnZW5kLnNwYWNpbmcueCA9IHVuaXQoMC41LCJjbSIpLAogICAgICAgIHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkFyaWFsIikpKQoKZ2dzYXZlKGY4YiwgZmlsZSA9ICIuLi8uLi9maWd1cmVzL0ZpZ3VyZThCX2xhbmRzY2FwZV9tZXRyaWNzLnBuZyIsIGRwaSA9IDMwMCkgIyBhZGp1c3QgZHBpIGFjY29yZGluZ2x5CgpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0KKGFyciA8LSBnZ2FycmFuZ2UoZjhiLGY4YSxucm93PTIsbmNvbD0xLGxhYmVscz1jKCJBIiwiQiIpLCBjb21tb24ubGVnZW5kID0gVCkpCmdnc2F2ZShhcnIsIGZpbGU9Ii4uLy4uL2ZpZ3VyZXMvRmlndXJlOF9MYW5kc2NhcGVfUGF0Y2hfTWV0cmljcy5wbmciLCBkcGk9MzAwKQpgYGAKClRoZSAxMC1tIGFzcGVuIGNsYXNzaWZpY2F0aW9uIGlkZW50aWZpZWQgWHggbW9yZSBwYXRjaCB0aGFuIHJlZmVyZW5jZSBpbWFnZXM6CgpgYGB7cn0KCnByaW50KCIlIERpZmZlcmVuY2UgaW4gbnVtYmVyIG9mIHBhdGNoZXMgYWNyb3NzIHRoZSBTb3V0aGVybiBSb2NraWVzOiAiKQphIDwtIGRmICU+JSBmaWx0ZXIocmVnaW9uPT0iU1JNRSIsbWV0cmljPT0iTnVtYmVyIG9mIFBhdGNoZXMiLHNvdXJjZT09IkFzcGVuMTBtIikKYiA8LSBkZiAlPiUgZmlsdGVyKHJlZ2lvbj09IlNSTUUiLG1ldHJpYz09Ik51bWJlciBvZiBQYXRjaGVzIixzb3VyY2U9PSJMRkVWVCIpCigoYiR2YWx1ZS1hJHZhbHVlKS9iJHZhbHVlKSoxMDAKCnByaW50KCIlIERpZmZlcmVuY2UgaW4gbnVtYmVyIG9mIHBhdGNoZXMgYWNyb3NzIHRoZSBXaGl0ZSBSaXZlciBORjogIikKYSA8LSBkZiAlPiUgZmlsdGVyKHJlZ2lvbj09IldSTkYiLG1ldHJpYz09Ik51bWJlciBvZiBQYXRjaGVzIixzb3VyY2U9PSJBc3BlbjEwbSIpCmIgPC0gZGYgJT4lIGZpbHRlcihyZWdpb249PSJXUk5GIixtZXRyaWM9PSJOdW1iZXIgb2YgUGF0Y2hlcyIsc291cmNlPT0iTEZFVlQiKQooKGIkdmFsdWUtYSR2YWx1ZSkvYiR2YWx1ZSkqMTAwCgpwcmludCgifn5+fn5+fn5+fn5+fiIpCgpwcmludCgifn5+fn5+fn5+fn5+fiIpCgpwcmludCgiJSBEaWZmZXJlbmNlIGluIHBhdGNoIGRlbnNpdHk6ICIpCmEgPC0gZGYgJT4lIGZpbHRlcihtZXRyaWM9PSJQYXRjaCBEZW5zaXR5Iixzb3VyY2U9PSJBc3BlbjEwbSIpCmIgPC0gZGYgJT4lIGZpbHRlcihtZXRyaWM9PSJQYXRjaCBEZW5zaXR5Iixzb3VyY2U9PSJMRkVWVCIpCigoYiR2YWx1ZS1hJHZhbHVlKS9iJHZhbHVlKSoxMDAKCnByaW50KCIlIERpZmZlcmVuY2UgaW4gdG90YWwgYXJlYToiKQphIDwtIGRmICU+JSBmaWx0ZXIobWV0cmljPT0iVG90YWwgYXJlYSAoa20yKSIsc291cmNlPT0iQXNwZW4xMG0iKQphYSA8LSBkZiAlPiUgZmlsdGVyKG1ldHJpYz09IlRvdGFsIGFyZWEgKGttMikiLHNvdXJjZT09IkFzcGVuMzBtIikKYiA8LSBkZiAlPiUgZmlsdGVyKG1ldHJpYz09IlRvdGFsIGFyZWEgKGttMikiLHNvdXJjZT09IkxGRVZUIikKKChiJHZhbHVlLWEkdmFsdWUpL2IkdmFsdWUpKjEwMAooKGIkdmFsdWUtYWEkdmFsdWUpL2IkdmFsdWUpKjEwMAoKcm0oYSxhYSxiKQoKYGBgCgoKIyBTdXBwbGVtZW50YWwKCkZpZ3VyZSBTMTogUGhlbm9sb2d5IGFjcm9zcyB0aGUgU1JNRSBzdHVkeSByZWdpb24KCmBgYHtyfQoKIyBTcGF0aWFsIG1hcChzKSBvZiBrZXkgcGhlbm9sb2dpY2FsIG1ldHJpY3MKCmJyZWFrc19kYXRlcyA8LSBhcy5EYXRlKGMoIjIwMjAtMDEtMDEiLCAiMjAyMS0wMS0wMSIsICIyMDIyLTAxLTAxIikpCmJyZWFrc19udW1lcmljIDwtIGFzLm51bWVyaWMoYnJlYWtzX2RhdGVzKSAgIyBDb252ZXJ0IHRvIG51bWVyaWMgKGRheXMgc2luY2UgMTk3MC0wMS0wMSwgYnkgZGVmYXVsdCkKbGFiZWxzX2RhdGVzIDwtIGZvcm1hdChicmVha3NfZGF0ZXMsICIlWS0lbS0lZCIpCgpwMSA8LSBnZ3Bsb3QoZGF0YT1ibG9ja3MpICsKICBnZW9tX3NmKGFlcyhmaWxsPU1hdHVyaXR5XzEpKSArCiAgc2NhbGVfZmlsbF9jb250aW51b3VzKGxvdyA9ICJsaWdodGdyZWVuIiwgaGlnaCA9ICJkYXJrZ3JlZW4iLAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBmdW5jdGlvbih4KSBmb3JtYXQoYXMuRGF0ZShhcy5udW1lcmljKHgpLCBvcmlnaW49IjE5NzAtMDEtMDEiKSwgIiVCICVkIikpICsKICBnZW9tX3NmKGRhdGE9c3JtZSxmaWxsPU5BLGNvbG9yPSJibGFjayIsc2l6ZT0yLjUpICsKICBsYWJzKGZpbGw9Ik1hdHVyaXR5IikgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfY29sb3VyYmFyKGJhcndpZHRoID0gMC41LCBiYXJoZWlnaHQgPSA1LjUsIHRpY2tzPUYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsLnBvc2l0aW9uID0gInJpZ2h0IiwgdGl0bGUucG9zaXRpb24gPSAidG9wIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwudGhlbWUgPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBzaXplID0gOCkpKSArCiAgdGhlbWVfdm9pZCgpCgpwMiA8LSBnZ3Bsb3QoZGF0YT1ibG9ja3MpICsKICBnZW9tX3NmKGFlcyhmaWxsPURvcm1hbmN5XzEpKSArCiAgc2NhbGVfZmlsbF9jb250aW51b3VzKGxvdyA9ICIjZmZmZmQ0IiwgaGlnaCA9ICIjOTkzNDA0IiwKICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gZnVuY3Rpb24oeCkgZm9ybWF0KGFzLkRhdGUoYXMubnVtZXJpYyh4KSwgb3JpZ2luPSIxOTcwLTAxLTAxIiksICIlQiAlZCIpKSArCiAgZ2VvbV9zZihkYXRhPXNybWUsZmlsbD1OQSxjb2xvcj0iYmxhY2siLHNpemU9Mi41KSArCiAgbGFicyhmaWxsPSJEb3JtYW5jeSIpICsKICBndWlkZXMoZmlsbCA9IGd1aWRlX2NvbG91cmJhcihiYXJ3aWR0aCA9IDAuNSwgYmFyaGVpZ2h0ID0gNS41LCB0aWNrcz1GLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbC5wb3NpdGlvbiA9ICJyaWdodCIsIHRpdGxlLnBvc2l0aW9uID0gInRvcCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsLnRoZW1lID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgc2l6ZSA9IDgpKSkgKwogIHRoZW1lX3ZvaWQoKQoKZ2dhcnJhbmdlKHAxLHAyKQoKIyAjIEFkZCBhIHN0YXRpc3RpY3MgY29sdW1uIGZvciBsZWdlbmQgKGNvdWxkIGRvIHRoaXMgZm9yIEYxIHRvbyAuLi4pCiMgYmxvY2tzLm0gPC0gcmVzaGFwZTI6Om1lbHQoCiMgICBibG9ja3MsIGlkLnZhcnMgPSBjKCJpZCIsImVsZXZhdGlvbl9tbiIpLCAKIyAgIG1lYXN1cmUudmFycyA9IGMoIkdyZWVudXBfMSIsIk1pZEdyZWVudXBfMSIsIk1hdHVyaXR5XzEiLCJQZWFrXzEiLAojICAgICAgICAgICAgICAgICAgICAiTWlkR3JlZW5kb3duXzEiLCJTZW5lc2NlbmNlXzEiLCJEb3JtYW5jeV8xIiksIHZhcmlhYmxlLm5hbWUgPSAibWV0cmljIgojICkKCiMgQWRkIGEgc3RhdGlzdGljcyBjb2x1bW4gZm9yIGxlZ2VuZCAoY291bGQgZG8gdGhpcyBmb3IgRjEgdG9vIC4uLikKYmxvY2tzLm0gPC0gcmVzaGFwZTI6Om1lbHQoCiAgYmxvY2tzLCBpZC52YXJzID0gYygiaWQiLCJlbGV2YXRpb25fbW4iKSwgCiAgbWVhc3VyZS52YXJzID0gYygiR3JlZW51cF8xIiwiTWF0dXJpdHlfMSIsIlBlYWtfMSIsIkRvcm1hbmN5XzEiKSwgdmFyaWFibGUubmFtZSA9ICJtZXRyaWMiCikgJT4lCiAgbXV0YXRlKG1ldHJpYyA9IGFzLmNoYXJhY3RlcihtZXRyaWMpLAogICAgICAgICBtZXRyaWMgPSBhcy5mYWN0b3IocmVjb2RlKG1ldHJpYywKICAgICAgICAgICAgICAgICAgICAgICAgICJHcmVlbnVwXzEiID0gIkdyZWVudXAiLAogICAgICAgICAgICAgICAgICAgICAgICAgIk1hdHVyaXR5XzEiID0gIk1hdHVyaXR5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICJQZWFrXzEiID0gIlBlYWsgR3JlZW5uZXNzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJEb3JtYW5jeV8xIiA9ICJEb3JtYW5jeSIpKSkKICAKYmxvY2tzLm0kbWV0cmljIDwtIGZhY3RvcihibG9ja3MubSRtZXRyaWMsIGxldmVscz1jKCdHcmVlbnVwJywgJ01hdHVyaXR5JywgJ1BlYWsgR3JlZW5uZXNzJywgJ0Rvcm1hbmN5JykpCgpoZWFkKGJsb2Nrcy5tKQoKIyBEb3QgcGxvdCBvZiBwaGVub2xvZ3kgbWV0cmljIGJ5IGVsZXZhdGlvbiBmb3IgYWxsIGJsb2NrcwoKKHA1IDwtIGdncGxvdChkYXRhPWJsb2Nrcy5tLCBhZXMoeD12YWx1ZSx5PWVsZXZhdGlvbl9tbikpICsKICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIixjb2xvdXI9ImdyYXk0MCIsIGZpbGw9ImdyYXk3MCIsIHNpemU9MC44KSArCiAgZ2VvbV9wb2ludChzaXplPTEuMikgKwogIGZhY2V0X3dyYXAofm1ldHJpYywgc2NhbGVzPSJmcmVlX3giKSArCiAgdGhlbWVfbGlnaHQoMTIpICsKICAgIGxhYnMoeD0iRGF0ZSIseT0iRWxldmF0aW9uIikpCgoocDYgPC0gZ2dwbG90KGRhdGE9YmxvY2tzLm0sIGFlcyh4PXZhbHVlLHk9ZWxldmF0aW9uX21uLGNvbG9yPW1ldHJpYykpICsKICBnZW9tX3BvaW50KHNpemU9MS4yKSArCiAgdGhlbWVfbGlnaHQoMTIpKQoKYGBgCgoKUk9DLCBGMSwgTUNDOgoKYGBge3J9CgphY2NtZWFzIDwtIHJlYWRfY3N2KCcuLi8uLi9kYXRhL3RhYnVsYXIvbW9kL3Jlc3VsdHMvYWNjbWVhc19wcm9wLmNzdicpCgojIENhbGN1bGF0ZSB0aGUgYXZlcmFnZXMgZm9yIGVhY2ggY3V0b2ZmIHZhbHVlIGFjcm9zcyBtb2RlbCBydW5zCgphY2NtZWFzLm1uIDwtIGFjY21lYXMgJT4lCiAgbXV0YXRlKGN1dG9mZiA9IGFzLmNoYXJhY3RlcihjdXRvZmYpKSAlPiUKICBncm91cF9ieShjdXRvZmYpICU+JQogIHN1bW1hcmlzZSgKICAgIHByTW4gPSBtZWFuKHByU2l6ZSxuYS5ybT1UKSwgIyBzaXplIG9mIHByZXNlbmNlIGRhdGEgKG1lYW4pCiAgICBwclNkID0gc2QocHJTaXplLG5hLnJtPVQpLCAjIHNpemUgb2YgcHJlc2VuY2UgZGF0YSAoc2QpCiAgICBiZ01uID0gbWVhbihiZ1NpemUsbmEucm09VCksICMgc2l6ZSBvZiBiYWNrZ3JvdW5kIGRhdGEgKG1lYW4pCiAgICBiZ1NkID0gc2QoYmdTaXplLG5hLnJtPVQpLCAjIHNpemUgb2YgYmFja2dyb3VuZCBkYXRhIChzZCkKICAgIGZwck1uID0gbWVhbihmcHIsbmEucm09VCksICMgRmFsc2UgUG9zaXRpdmUgUmF0ZSAobWVhbikKICAgIGZwclNkID0gc2QoZnByLG5hLnJtPVQpLAogICAgdHByTW4gPSBtZWFuKHRwcixuYS5ybT1UKSwgIyBUcnVlIFBvc2l0aXZlIFJhdGUgKG1lYW4pCiAgICB0cHJTZCA9IHNkKHRwcixuYS5ybT1UKSwKICAgIHByZWNpc2lvbiA9IG1lYW4ocHJlY2lzaW9uLG5hLnJtPVQpLCAjIHByZWNpc2lvbiAobWVhbikKICAgIHByZWNpc2lvblNkID0gc2QocHJlY2lzaW9uLG5hLnJtPVQpLCAKICAgIHJlY2FsbCA9IG1lYW4ocmVjYWxsLG5hLnJtPVQpLCAjIHJlY2FsbCAobWVhbikKICAgIHJlY2FsbFNkID0gc2QocmVjYWxsLG5hLnJtPVQpLAogICAgZjEgPSBtZWFuKGYxLG5hLnJtPVQpLCAjIEYxIHNjb3JlIChtZWFuKQogICAgZjFTZCA9IHNkKGYxLG5hLnJtPVQpLAogICAgZ21lYW4gPSBtZWFuKGdtZWFuLG5hLnJtPVQpLCAjIEdlb21ldHJpYyBNZWFuIChtZWFuKQogICAgZ21lYW5TZCA9IHNkKGdtZWFuLG5hLnJtPVQpLAogICAgbWNjID0gbWVhbihtY2MsbmEucm09VCksICMgTWF0dGhldydzIENvcnJlbGF0aW9uIENvZWZmaWNpZW50IChtZWFuKQogICAgbWNjU2QgPSBzZChtY2MsbmEucm09VCksCiAgICBhY2N1cmFjeSA9IG1lYW4oYWNjdXJhY3ksbmEucm09VCksICMgT3ZlcmFsbCBhY2N1cmFjeSAobWVhbikKICAgIGFjY3VyYWN5U2QgPSBzZChhY2N1cmFjeSxuYS5ybT1UKQogICkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShjdXRvZmYgPSByb3VuZChhcy5kb3VibGUoY3V0b2ZmKSwzKSkKCiMgQ2FsY3VsYXRlIG9wdGltdW0gdGhyZXNob2xkIGJhc2VkIG9uIEYxIHN0YXRpc3RpYwoKY3V0b2ZmT3B0TW4gPSBhY2NtZWFzLm1uW3doaWNoLm1heChhY2NtZWFzLm1uJGYxKSxdJGN1dG9mZgpwcmVjaXNpb25PcHRGMU1uID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRmMSksXSRwcmVjaXNpb24KcmVjYWxsT3B0RjFNbiA9IGFjY21lYXMubW5bd2hpY2gubWF4KGFjY21lYXMubW4kZjEpLF0kcmVjYWxsCmZwck9wdE1uID0gYWNjbWVhcy5tblt3aGljaC5tYXgoYWNjbWVhcy5tbiRmMSksXSRmcHJNbgp0cHJPcHRNbiA9IGFjY21lYXMubW5bd2hpY2gubWF4KGFjY21lYXMubW4kZjEpLF0kdHByTW4KbWF4X2YxIDwtIG1heChhY2NtZWFzLm1uJGYxLCBuYS5ybSA9IFRSVUUpCgpgYGAKCgpgYGB7ciB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KCiMgUk9DIEN1cnZlIGFuZCBsYWJlbCB0aGUgQVVDIHZhbHVlIChhcHByb3hpbWF0aW9uKQoKIyBHZXQgdGhlIEFVQyBhcHByb3gKCiMgRW5zdXJlIGRhdGEgaXMgc29ydGVkIGJ5IEZQUgphY2NtZWFzX21uX3NvcnRlZCA8LSBhY2NtZWFzLm1uW29yZGVyKGFjY21lYXMubW4kZnByTW4pLCBdCiMgQ29tcHV0ZSBBVUMgdXNpbmcgdHJhcHogZnVuY3Rpb24gaW4gJ3ByYWNtYScKYXVjX2F2Z19hcHByb3ggPC0gdHJhcHooYWNjbWVhc19tbl9zb3J0ZWQkZnByTW4sIGFjY21lYXNfbW5fc29ydGVkJHRwck1uKQoKZnMyYSA8LSBnZ3Bsb3QoZGF0YT1hY2NtZWFzKSArCiAgZ2VvbV9saW5lKGFlcyh4PWZwcix5PXRwcixjb2xvcj1mYWN0b3IobW9kZWwpKSxsaW5ld2lkdGg9MC40KSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbj0idHVyYm8iKSArCiAgZ2VvbV9saW5lKGRhdGE9YWNjbWVhcy5tbiwgYWVzKHggPSBmcHJNbiwgeSA9IHRwck1uKSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMC44KSArICAjIFJPQyBjdXJ2ZSBhdmVyYWdlCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cz1jKDAsMSkpICsgICMgU2V0IHggYXhpcyBsaW1pdHMgZnJvbSAwIHRvIDEKICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzPWMoMCwxKSkgKwogIGxhYnMoeD0nRmFsc2UgUG9zaXRpdmUgUmF0ZScsIHk9J1RydWUgUG9zaXRpdmUgUmF0ZScsIHRhZz0iQSIpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gZnByT3B0TW4sIHkgPSB0cHJPcHRNbiksCiAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLCBzaXplID0gMywgc2hhcGUgPSAxOSkgKyAgIyBvcHRpbWFsIHRocmVzaG9sZCBwb2ludAogIGdlb21fdGV4dChhZXMoeD1mcHJPcHRNbiwgeT10cHJPcHRNbiksCiAgICAgICAgICAgIGxhYmVsID0gcGFzdGUwKCdPcHRpbWFsIHRocmVzaG9sZDogJywgcm91bmQoY3V0b2ZmT3B0TW4sMyksJ1xuQXBwcm94LiBBVUM6ICcsIHJvdW5kKGF1Y19hdmdfYXBwcm94LDMpKSwKICAgICAgICAgICAgbnVkZ2VfeCA9IDAuMzgsIG51ZGdlX3kgPSAtMC4wOCwgc2l6ZSA9IDMuNSkgKwogIGNvb3JkX2ZpeGVkKHJhdGlvID0gMSkgKyAgIyB0byBrZWVwIHRoZSB4IGFuZCB5IGF4ZXMgc2NhbGVzIHNhbWUKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImJsYWNrIikgKyAgIyBkaWFnb25hbAogIHRoZW1lX2xpZ2h0KCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMCwwLjQsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplPTExLGZhY2U9Iml0YWxpYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IHVuaXQoYygwLjQsMCwwLDApLCAiY20iKSksCiAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEpKQoKZnMyYiA8LSBnZ3Bsb3QoZGF0YT1hY2NtZWFzKSArCiAgZ2VvbV9saW5lKGFlcyh4PWN1dG9mZix5PWYxLGNvbG9yPWZhY3Rvcihtb2RlbCkpLGxpbmV3aWR0aD0wLjQpICsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uPSJ0dXJibyIpICsKICBnZW9tX2xpbmUoZGF0YT1hY2NtZWFzLm1uLCBhZXMoeD1jdXRvZmYsIHkgPSBmMSksIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDAuOCkgKyAgIyBST0MgY3VydmUgYXZlcmFnZQogICMgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTAuNDI0LCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3I9ImJsYWNrIikgKwogIGdlb21fcG9pbnQoYWVzKHg9Y3V0b2ZmT3B0TW4sIHk9bWF4X2YxKSwKICAgICAgICAgICAgIGNvbG9yID0gInJlZCIsIHNpemUgPSAzLCBzaGFwZSA9IDE5KSArCiAgZ2VvbV90ZXh0KGFlcyh4PWN1dG9mZk9wdE1uLCB5PW1heF9mMSksCiAgICAgICAgICAgIGxhYmVsID0gcGFzdGUwKCdPcHRpbWFsIHRocmVzaG9sZDogJywgcm91bmQoY3V0b2ZmT3B0TW4sMykpLAogICAgICAgICAgICBudWRnZV94ID0gMC4wLCBudWRnZV95ID0gMC4wOCwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHM9YygwLDEpKSArICAjIFNldCB4IGF4aXMgbGltaXRzIGZyb20gMCB0byAxCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cz1jKDAsMSkpICsKICBsYWJzKHg9J1RocmVzaG9sZCcsIHk9J0YxIFNjb3JlJywgdGFnPSJCIikgKwogIGNvb3JkX2ZpeGVkKHJhdGlvID0gMSkgKyAgIyB0byBrZWVwIHRoZSB4IGFuZCB5IGF4ZXMgc2NhbGVzIHNhbWUKICB0aGVtZV9saWdodCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTEsZmFjZT0iaXRhbGljIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFyZ2luID0gdW5pdChjKDAsMC40LDAsMCksICJjbSIpKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxmYWNlPSJpdGFsaWMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSB1bml0KGMoMC40LDAsMCwwKSwgImNtIikpLAogICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAgICAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDExKSkKCihmczIgPC0gZ2dhcnJhbmdlKGZzMmEsZnMyYikpCgpnZ3NhdmUoZnMyLCBmaWxlPSIuLi8uLi9maWd1cmVzL0ZpZ3VyZVMyX0FVQ19GMU1heC5wbmciLCBkcGk9MzAwKQoKcm0oZnMyLGZzMmEsZnMyYixhY2NtZWFzX21uX3NvcnRlZCxhdWNfYXZnX2FwcHJveCkKCmBgYAoKVGFibGUgUzE6IFBlcmZvcm1hbmNlIG1ldHJpY3MgZm9yIHRoZSBvcHRpbXVtIGN1dG9mZiB2YWx1ZQoKQ3JlYXRlIGEgcHJldHR5IHRhYmxlLgoKYGBge3J9CgpsaWJyYXJ5KGZsZXh0YWJsZSwgcXVpZXRseSA9IFQpCgojIEdldCB0aGUgYmVzdCBGMSBzY29yZSBmb3IgZWFjaCBtb2RlbCBydW4KYWNjbWVhcy5iZXN0IDwtIGFjY21lYXMgJT4lIAogIG11dGF0ZShtb2RlbCA9IGFzLmZhY3Rvcihtb2RlbCkpICU+JQogIGdyb3VwX2J5KG1vZGVsKSAlPiUgCiAgdG9wX24oMSxmMSkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShtb2RlbF9pdGVyID0gcm93X251bWJlcigpKSAlPiUKICBzZWxlY3QobW9kZWwscHJTaXplLGJnU2l6ZSx0cHIscHJlY2lzaW9uLHJlY2FsbCxmMSxjdXRvZmYpCgpoZWFkKGFjY21lYXMuYmVzdCwxMCkKCiMgRml4IG5hbWVzIGFuZCBhZGQgdW5pdHMKbmFtZXMoYWNjbWVhcy5iZXN0KSA8LSBjKCJNb2RlbCBTZWVkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICIjIG9mIFByZXNlbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICIjIG9mIEJhY2tncm91bmQiLAogICAgICAgICAgICAgICAgICAgICAgICAgIlRydWUgUG9zaXRpdmUgUmF0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAiUHJlY2lzaW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJSZWNhbGwiLAogICAgICAgICAgICAgICAgICAgICAgICAgIkYxLVNjb3JlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICJUaHJlc2hvbGQiKQoKIyBTZXQgZm9udCBuYW1lIGZvciB0YWJsZQpmb250bmFtZSA8LSAiVGltZXMgTmV3IFJvbWFuIgoKIyBDcmVhdGUgdGhlIGZsZXh0YWJsZQooZnQxIDwtIGZsZXh0YWJsZShhY2NtZWFzLmJlc3QpICU+JQogZm9udChmb250bmFtZSA9IGZvbnRuYW1lLCBwYXJ0ID0gImFsbCIpICU+JQogYXV0b2ZpdCgpICU+JSBmaXRfdG9fd2lkdGgoNi41KSkKcHJpbnQoZnQxLCBwcmV2aWV3ID0gImRvY3giKQoKIyBXcml0ZSB0byBhIENTVgp3cml0ZV9jc3YoYWNjbWVhcy5iZXN0LCAiLi4vLi4vZmlndXJlcy9UYWJsZVMxX0FjY3VyYWN5X01heEYxLmNzdiIpCgpgYGAKCkNsZWFuIHVwIQoKYGBge3J9CgpgYGAK